quick recap

You now have both while loop and for loop in your toolset. Let's look quickly at yesterday's last tutorial task. However, I also will also upload general solution notebook files later today)


In [1]:
for fIndex, y in enumerate(range(2, 5)):
    
    countdown = y
    yFactorial = 1
    
    wIndex = 0
    while countdown > 1:
        yFactorial *= countdown
        
        ##### CHECKPOINT! #####
        
        countdown -= 1
        wIndex += 1
    
    print("RESULT: %d! = %d" % (y, yFactorial))


RESULT: 2! = 2
RESULT: 3! = 6
RESULT: 4! = 24

In [2]:
# Question 3

print("%s %s %s %s %s" % ("fIndex", "y", "wIndex", "countdown", "yFactorial"))

for fIndex, y in enumerate(range(2, 5)):
    
    countdown = y
    yFactorial = 1
    
    wIndex = 0
    while countdown > 1:
        yFactorial *= countdown
        
        print("%-6i %1i %6i %9i %10i" % (fIndex, y, wIndex, countdown, yFactorial))
        
        countdown -= 1
        wIndex += 1
    
    #print "RESULT: %d! = %d" % (y, yFactorial)


fIndex y wIndex countdown yFactorial
0      2      0         2          2
1      3      0         3          3
1      3      1         2          6
2      4      0         4          4
2      4      1         3         12
2      4      2         2         24

Today

  • Sublists
  • nested lists
  • Functions (the most fun object in Python in my mind)
  • global vs local variables
  • docstrings

Extracting Sublists

Sometimes we want to operate on only parts of lists. The syntax for this is particularly simple:


In [3]:
# create our favorite massRatios:
massRatios = list(range(10))
massRatios


Out[3]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [6]:
massRatios[2:7]


Out[6]:
[2, 3, 4, 5, 6]

This is called slicing and the 2 parameters required are separated by a colon :.

Similar to the parameters for the range() function, the starting number is inclusive while the ending number is exclusive.

When the 1st parameter is left out, the slice starts at the beginning of the list, when the last parameter is left out it goes until the end:


In [7]:
print(massRatios[:4])
print(massRatios[4:])


[0, 1, 2, 3]
[4, 5, 6, 7, 8, 9]

Note how in the first case, the length returned is the same as the value of the index you provide, thanks to 0-based indexing.

Note, also, that thanks to the asymmetry of inclusivity for the start parameter vs exclusivity for the end parameter, you can use the same number twice to get both ends of a list, thisk creates easier to read code as well.


In [8]:
i = 5
print(massRatios[:i])
print(massRatios[i:])


[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]

Nested lists

Python allows for nesting lists. This allows for finer substructure of data storage. For example, storing vectors in a list:


In [9]:
v1 = [0,1,2]
v2 = [7,8,9]

In [10]:
vectors = [v1, v2]
vectors


Out[10]:
[[0, 1, 2], [7, 8, 9]]

When accessing elements, you can also just nest the indexing:


In [11]:
vectors[0][1]


Out[11]:
1

In [12]:
vectors[1][-1]


Out[12]:
9

Functions

Functions are useful because they allow us to perform operations many times without repeating code snippets, keeping programs shorter, more managable, and more organized. We will start with the Planck function,

$B_{\lambda}(T) = \frac{2 h c^2}{\lambda^5 \left[\exp\left(\frac{h c}{\lambda k T}\right) - 1 \right]}$

where $h$ is Planck's constant, $c$ is the speed of light, $k$ is Boltzmann's constant, $T$ is the blackbody temperature, and $\lambda$ is the wavelength.


In [13]:
# First, define the physical constants:
h = 6.626e-34  # J s, Planck's constant
k = 1.38e-23   # J K^-1, Boltzmann constant
c = 3.00e8     # m s^-1, speed of light
 
# Conversion between angstroms and meters
angPerM = 1e10
    
# The Planck function is a function of two variables;
# for now, we'll set T = 5,800 K, the photospheric temperature of the Sun
# and allow the wavelength to vary.
temp = 5800.0  

from math import exp

# Define the function using def:
 
def intensity1(waveAng):               # Function header
    waveM = waveAng / angPerM    # Will convert Angstroms to meters
    
    B = 2 * h * c**2 / (waveM**5 * (exp(h * c / (waveM * k * temp)) - 1))
    
    return B

# Units will be W / m^2 / m / ster
The above statement comprises the function body & return to the main program.

In [16]:
print('%e' % intensity1(5000.0))  # note the %e formatting string for exponent notation


2.676462e+13
Basic structure: def function_name(argument): ... Notice especially: def, colon, indent Optional: argument (or "input": you'll hear people say both), return statement NOTE: Without a return statement, the function will still return None. More on None to come!

In [18]:
def funcNoReturn(x):
    print("Answer:", x + 5)
    return x+5

y = funcNoReturn(6)
print("y =", y)


Answer: 11
y = 11
Next we'll create a list of wavelengths at which to evaluate the Planck function:

In [19]:
waveList = [3000 + 100 * i for i in range(41)]

Q. What did the above command do?


In [20]:
waveList


Out[20]:
[3000,
 3100,
 3200,
 3300,
 3400,
 3500,
 3600,
 3700,
 3800,
 3900,
 4000,
 4100,
 4200,
 4300,
 4400,
 4500,
 4600,
 4700,
 4800,
 4900,
 5000,
 5100,
 5200,
 5300,
 5400,
 5500,
 5600,
 5700,
 5800,
 5900,
 6000,
 6100,
 6200,
 6300,
 6400,
 6500,
 6600,
 6700,
 6800,
 6900,
 7000]
Now, we'll create an intensity list using another list comprehension:

In [22]:
intensityList = [intensity1(wave) for wave in waveList]  
intensityList


Out[22]:
[12467562222465.664,
 13822570363885.275,
 15150103234939.592,
 16435485924635.223,
 17666286862798.945,
 18832325062754.484,
 19925592398349.105,
 20940115580525.785,
 21871778764398.547,
 22718123815804.715,
 23478141556576.08,
 24152063991194.957,
 24741164684060.684,
 25247572123612.84,
 25674099050299.133,
 26024089289297.02,
 26301282555334.36,
 26509696923790.062,
 26653528131078.855,
 26737064526083.25,
 26764616298487.527,
 26740457522211.934,
 26668779542671.52,
 26553654281462.41,
 26399006112811.066,
 26208591068650.652,
 25985982243070.82,
 25734560384567.684,
 25457508780698.863,
 25157811650863.527,
 24838255366738.516,
 24501431915135.992,
 24149744104143.562,
 23785412090302.13,
 23410480872544.707,
 23026828458155.78,
 22636174457744.527,
 22240088910847.316,
 21840001181999.707,
 21437208799649.094,
 21032886137786.3]

Q. What should the output of "intensityList" be?

For a nice print-out, make use of a for loop and the range function:

In [23]:
for index in range(len(waveList)):
    print('wavelength (Angstrom) = {}      Intensity (W m^-3 ster^-1) = {:.2e}'\
          .format(waveList[index], intensityList[index]))


wavelength (Angstrom) = 3000      Intensity (W m^-3 ster^-1) = 1.25e+13
wavelength (Angstrom) = 3100      Intensity (W m^-3 ster^-1) = 1.38e+13
wavelength (Angstrom) = 3200      Intensity (W m^-3 ster^-1) = 1.52e+13
wavelength (Angstrom) = 3300      Intensity (W m^-3 ster^-1) = 1.64e+13
wavelength (Angstrom) = 3400      Intensity (W m^-3 ster^-1) = 1.77e+13
wavelength (Angstrom) = 3500      Intensity (W m^-3 ster^-1) = 1.88e+13
wavelength (Angstrom) = 3600      Intensity (W m^-3 ster^-1) = 1.99e+13
wavelength (Angstrom) = 3700      Intensity (W m^-3 ster^-1) = 2.09e+13
wavelength (Angstrom) = 3800      Intensity (W m^-3 ster^-1) = 2.19e+13
wavelength (Angstrom) = 3900      Intensity (W m^-3 ster^-1) = 2.27e+13
wavelength (Angstrom) = 4000      Intensity (W m^-3 ster^-1) = 2.35e+13
wavelength (Angstrom) = 4100      Intensity (W m^-3 ster^-1) = 2.42e+13
wavelength (Angstrom) = 4200      Intensity (W m^-3 ster^-1) = 2.47e+13
wavelength (Angstrom) = 4300      Intensity (W m^-3 ster^-1) = 2.52e+13
wavelength (Angstrom) = 4400      Intensity (W m^-3 ster^-1) = 2.57e+13
wavelength (Angstrom) = 4500      Intensity (W m^-3 ster^-1) = 2.60e+13
wavelength (Angstrom) = 4600      Intensity (W m^-3 ster^-1) = 2.63e+13
wavelength (Angstrom) = 4700      Intensity (W m^-3 ster^-1) = 2.65e+13
wavelength (Angstrom) = 4800      Intensity (W m^-3 ster^-1) = 2.67e+13
wavelength (Angstrom) = 4900      Intensity (W m^-3 ster^-1) = 2.67e+13
wavelength (Angstrom) = 5000      Intensity (W m^-3 ster^-1) = 2.68e+13
wavelength (Angstrom) = 5100      Intensity (W m^-3 ster^-1) = 2.67e+13
wavelength (Angstrom) = 5200      Intensity (W m^-3 ster^-1) = 2.67e+13
wavelength (Angstrom) = 5300      Intensity (W m^-3 ster^-1) = 2.66e+13
wavelength (Angstrom) = 5400      Intensity (W m^-3 ster^-1) = 2.64e+13
wavelength (Angstrom) = 5500      Intensity (W m^-3 ster^-1) = 2.62e+13
wavelength (Angstrom) = 5600      Intensity (W m^-3 ster^-1) = 2.60e+13
wavelength (Angstrom) = 5700      Intensity (W m^-3 ster^-1) = 2.57e+13
wavelength (Angstrom) = 5800      Intensity (W m^-3 ster^-1) = 2.55e+13
wavelength (Angstrom) = 5900      Intensity (W m^-3 ster^-1) = 2.52e+13
wavelength (Angstrom) = 6000      Intensity (W m^-3 ster^-1) = 2.48e+13
wavelength (Angstrom) = 6100      Intensity (W m^-3 ster^-1) = 2.45e+13
wavelength (Angstrom) = 6200      Intensity (W m^-3 ster^-1) = 2.41e+13
wavelength (Angstrom) = 6300      Intensity (W m^-3 ster^-1) = 2.38e+13
wavelength (Angstrom) = 6400      Intensity (W m^-3 ster^-1) = 2.34e+13
wavelength (Angstrom) = 6500      Intensity (W m^-3 ster^-1) = 2.30e+13
wavelength (Angstrom) = 6600      Intensity (W m^-3 ster^-1) = 2.26e+13
wavelength (Angstrom) = 6700      Intensity (W m^-3 ster^-1) = 2.22e+13
wavelength (Angstrom) = 6800      Intensity (W m^-3 ster^-1) = 2.18e+13
wavelength (Angstrom) = 6900      Intensity (W m^-3 ster^-1) = 2.14e+13
wavelength (Angstrom) = 7000      Intensity (W m^-3 ster^-1) = 2.10e+13

Q. What will the output look like?

Local and Global variables

When I define a function in the following manner,

In [26]:
def intensity1(waveAng):                # Function header
    waveM = waveAng / angPerM    # Will convert Angstroms to meters
    
    B = 2 * h * c**2 / (waveM**5 * (exp(h * c / (waveM * k * temp)) - 1))
    
    return B
B is a local variable -- it only exists in the function Intensity
So this fails:

In [25]:
B


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-25-30cf3d7d133b> in <module>()
----> 1 B

NameError: name 'B' is not defined
so does:

In [27]:
waveM


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-27-65e6b9a18303> in <module>()
----> 1 waveM

NameError: name 'waveM' is not defined
In contrast, h, k, c, and temp are global variables (defined above) and can be accessed anywhere in the program or notebook. Notes on global and local variables: * Avoid local and global variables with the same name. (Works, but can be confusing) * When there are variables of the same name, Python first looks for a local variable, then a global variable, then a built-in function of that name. * Avoid changing global variables in functions, although Python has a utility for doing so: the global function.

Q. What will this print?


In [34]:
g = 10

def f(x):
    g = 11
    return x + g

f(5), g


Out[34]:
(16, 10)
But:

In [37]:
g = 10

def f(x):
    global g       # Now "g" inside the function references the global variable
    g = 11         # Give that variable a new value
    return x + g

f(5), g


Out[37]:
(16, 11)
Use of "global" is generally frowned upon (dangerous), but here it is for completeness.

Functions with multiple arguments

The Planck function is a function of wavelength AND temperature.

In [39]:
def intensity2(waveAng, temp):   # 2nd version of function Intensity
    waveM = waveAng / angPerM
    B = 2 * h * c**2 / (waveM**5 * (exp(h * c / (waveM * k * temp)) - 1))
    return B

In [40]:
intensity2(5000.0, 5800.0)


Out[40]:
26764616298487.527
"call sequence" simple, nothing fancy! Just comma-separated values to supply multiple arguments.
But, you could also call the function with names for arguments:

In [42]:
intensity2(temp=5800.0, waveAng=5000.0)


Out[42]:
26764616298487.527
or

In [43]:
intensity2(waveAng=5000.0, temp=5800.0)


Out[43]:
26764616298487.527

Q. Will this work (produce the same result)?


In [44]:
intensity2(5800.0, 5000.0)


Out[44]:
12742982172276.992
Functions are useful beyond just evaluating equations. Here's another example (albeit another mathematical one). We needed a wavelength list for the previous example with the Planck function; let's write a function to make that for us.

In [45]:
def waveListGen(minWave, maxWave, delta):
    waveList = []
    
    wave = minWave
    
    while wave <= maxWave:
        waveList.append(wave)
        wave += delta
    
    return waveList

Q. What will this do?


In [46]:
waveList = waveListGen(3000, 5000, 200)
waveList


Out[46]:
[3000, 3200, 3400, 3600, 3800, 4000, 4200, 4400, 4600, 4800, 5000]
Note that the waveListGen function we just wrote is more flexible than our previous approach, wavelengthList = [3000.0 + 100.0 * i for i in range(41)] since we can easily modify the start, stop, and wavelength spacing. On the other hand, we could just use range:

In [47]:
list(range(3000, 5001, 200))


Out[47]:
[3000, 3200, 3400, 3600, 3800, 4000, 4200, 4400, 4600, 4800, 5000]

Functions with multiple return values

Given a wavelength, we can return the frequency and the value of the Planck function at that frequency:

In [49]:
# (Defined h, c, k above and imported math)

def intensity3(waveAng, temp):   # 3rd version of function Intensity
    waveM = waveAng / angPerM
    
    B = 2 * h * c**2 / (waveM**5 * (exp(h * c / (waveM * k * temp)) - 1))
    
    return (waveAng, B)

In [50]:
temp = 10000.0  # Hot A star or cool B star; brighter than a G star

waveAng, intensity = intensity3(6000.0, temp=temp)
waveAng, intensity   # notice the automatic packing of Python again


Out[50]:
(6000.0, 152903075323564.03)
There must be two variables on the left-hand side of the assignment operator since the function will return two variables, or else if there is only only variable it will contain both values as a tuple (see cell below). This is yet another instance of "unpacking," which we saw while using the "enumerate" function, and when working with tuples.

In [51]:
result = intensity3(6000.0, 10000.0)

print(result)
type(result)


(6000.0, 152903075323564.03)
Out[51]:
tuple
We've already seen how to make nice table outputs, so let's do it here:

In [67]:
for wave in waveListGen(3e3, 4e3, 100):
    print('Wavelength (Angstroms) = %-10i Intensity (W m^-3 ster^-1) = %.2e'\
          % intensity3(wave, 1e4))


Wavelength (Angstroms) = 3000       Intensity (W m^-3 ster^-1) = 4.07e+14
Wavelength (Angstroms) = 3100       Intensity (W m^-3 ster^-1) = 4.04e+14
Wavelength (Angstroms) = 3200       Intensity (W m^-3 ster^-1) = 3.99e+14
Wavelength (Angstroms) = 3300       Intensity (W m^-3 ster^-1) = 3.92e+14
Wavelength (Angstroms) = 3400       Intensity (W m^-3 ster^-1) = 3.85e+14
Wavelength (Angstroms) = 3500       Intensity (W m^-3 ster^-1) = 3.77e+14
Wavelength (Angstroms) = 3600       Intensity (W m^-3 ster^-1) = 3.68e+14
Wavelength (Angstroms) = 3700       Intensity (W m^-3 ster^-1) = 3.58e+14
Wavelength (Angstroms) = 3800       Intensity (W m^-3 ster^-1) = 3.48e+14
Wavelength (Angstroms) = 3900       Intensity (W m^-3 ster^-1) = 3.37e+14
Wavelength (Angstroms) = 4000       Intensity (W m^-3 ster^-1) = 3.27e+14
The %-10i prints real numbers with ten spaces, left justified. By default (i.e., no minus sign), columns are right justified.
Notice how compact that for loop is! We initialized the list of wavelengths right in the for loop, then, passed the results of the calculation (using the function Intensity3) directly to string formatting. This is possible because the Intensity3 returns a tuple!

Doc Strings:

Doc strings are used to document functions. They generally include:

  • A description of the functionality of the function

  • A list of arguments

  • A description of outputs (returned values)

And, they serve as the help documentation!

They go right after the function header and are enclosed within triple quotes.


In [52]:
def force(mass1, mass2, radius):
    """
    Compute the gravitational force between two bodies.
    
    Parameters
    ----------
    mass1 : int, float
        Mass of the first body, in kilograms.
    mass2 : int, float
        Mass of the second body, in kilograms.
    radius : int, float
        Separation of the bodies, in meters.

    Example
    -------
    To compute force between Earth and the Sun:
    >>> F = force(5.97e24, 1.99e30, 1.5e11)

    Returns
    -------
    Force in Newtons : float
    """
    G = 6.67e-11
    
    return G * mass1 * mass2 / radius**2

In [53]:
result = force(5.97e24, 2.00e30, 1.5e11)
result


Out[53]:
3.539546666666667e+22

In [54]:
# To see the documentation for a function, use help:

help(force)


Help on function force in module __main__:

force(mass1, mass2, radius)
    Compute the gravitational force between two bodies.
    
    Parameters
    ----------
    mass1 : int, float
        Mass of the first body, in kilograms.
    mass2 : int, float
        Mass of the second body, in kilograms.
    radius : int, float
        Separation of the bodies, in meters.
    
    Example
    -------
    To compute force between Earth and the Sun:
    >>> F = force(5.97e24, 1.99e30, 1.5e11)
    
    Returns
    -------
    Force in Newtons : float

or with the subwindow:


In [55]:
force?

Some important functionality review


In [73]:
# a = []          initialize an empty list
# a = [1., 2]     initialize a list
# a.append(elem)  add the elem object to the end of the list
# a + [5, 4]      concatenate (join) two lists
# a.insert(i, e)  insert element e at index i
# a[5]            acess the value of the element at index 5
# a[-1]           get the last list element value
# a[4:7]          slice list a
# del a[i]        delete list element with index i
# a.remove(e)     remove list element with value e (not index e)
# a.index('test') find the index where the element has the value 'test'
# 4 in a          find out whether 4 is in a
# a.count(4)      count how many times 4 is in a
# len(a)          return the number of elements in a
# min(a)          return the smallest element in a
# max(a)          return the largest element in a
# sum(a)          add all the elements in a
# sorted(a)       return a sorted version of list a
# reversed(a)     return a reversed version of list a
# a[1][0][4]      nested list indexing (3 dimensional list in this case)