Learning objectives:
Say we want to add $2$ to all the numbers in a list:
v = [-1, 1, 10]
for e in v:
e=e+2
print(v)
[-1, 1, 10]
We can see that the list v is unaltered! The reason for this is that inside the loop, e is an ordinary (int) variable. At the start of the iteration e is assigned a copy of the next element in the list. Inside the loop we can change e but v itself is unaltered. If we want to change v we have to use an index to access and modify its elements:
v[1] = 4 # assign 4 to 2nd element (index 1) in v
print(v)
[-1, 4, 10]
To add 2 to all values we need a for loop over indices:
for i in range(len(v)):
v[i] = v[i] + 2
print(v)
[1, 6, 12]
Consider how we can loop over elements in both Cdegrees and Fdegrees at the same time. One approach would be to use list indices:
# First we have to recreate the data from the previous lecture
Cdegrees = [deg for deg in range(-20, 41, 5)]
Fdegrees = [(9/5)*deg + 32 for deg in Cdegrees]
for i in range(len(Cdegrees)):
print(Cdegrees[i], Fdegrees[i])
-20 -4.0 -15 5.0 -10 14.0 -5 23.0 0 32.0 5 41.0 10 50.0 15 59.0 20 68.0 25 77.0 30 86.0 35 95.0 40 104.0
An alternative construct (regarded as more ”Pythonic”) uses the zip function:
for C, F in zip(Cdegrees, Fdegrees):
print(C, F)
-20 -4.0 -15 5.0 -10 14.0 -5 23.0 0 32.0 5 41.0 10 50.0 15 59.0 20 68.0 25 77.0 30 86.0 35 95.0 40 104.0
Another example with three lists:
l1 = [3, 6, 1]; l2 = [1.5, 1, 0]; l3 = [9.1, 3, 2]
for e1, e2, e3 in zip(l1, l2, l3):
print(e1, e2, e3)
3 1.5 9.1 6 1 3 1 0 2
If the lists are of unequal length then the loop stops when the end of the shortest list is reached. Experiment with this:
l1 = [3, 6, 1, 4, 6]; l2 = [1.5, 1, 0, 7]; l3 = [9.1, 3, 2, 0, 9]
for e1, e2, e3 in zip(l1, l2, l3):
print(e1, e2, e3)
3 1.5 9.1 6 1 3 1 0 2 4 7 0
A list can contain any object, including another list. To illustrate this, consider how to store the conversion table as a single Python list rather than two separate lists.
Cdegrees = range(-20, 41, 5)
Fdegrees = [(9.0/5)*C + 32 for C in Cdegrees]
table1 = [Cdegrees, Fdegrees] # List of two lists
print("table1 = ", table1)
print("table1[0] = ", table1[0])
print("table1[1] = ", table1[1])
print("table1[1][3] = ", table1[1][3])
table1 = [range(-20, 41, 5), [-4.0, 5.0, 14.0, 23.0, 32.0, 41.0, 50.0, 59.0, 68.0, 77.0, 86.0, 95.0, 104.0]] table1[0] = range(-20, 41, 5) table1[1] = [-4.0, 5.0, 14.0, 23.0, 32.0, 41.0, 50.0, 59.0, 68.0, 77.0, 86.0, 95.0, 104.0] table1[2][3] = 23.0
This gives us a table of two rows. How do we create a table of columns instead:
table2 = []
for C, F in zip(Cdegrees, Fdegrees):
row = [C, F]
table2.append(row)
print(table2)
[[-20, -4.0], [-15, 5.0], [-10, 14.0], [-5, 23.0], [0, 32.0], [5, 41.0], [10, 50.0], [15, 59.0], [20, 68.0], [25, 77.0], [30, 86.0], [35, 95.0], [40, 104.0]]
We can use list comprehension to do this more elegantly:
table2 = [[C, F] for C, F in zip(Cdegrees, Fdegrees)]
print(table2)
[[-20, -4.0], [-15, 5.0], [-10, 14.0], [-5, 23.0], [0, 32.0], [5, 41.0], [10, 50.0], [15, 59.0], [20, 68.0], [25, 77.0], [30, 86.0], [35, 95.0], [40, 104.0]]
And you can loop through this list as before:
for C, F in table2:
print(C, F)
-20 -4.0 -15 5.0 -10 14.0 -5 23.0 0 32.0 5 41.0 10 50.0 15 59.0 20 68.0 25 77.0 30 86.0 35 95.0 40 104.0
t = (2, 4, 6, 'temp.pdf') # Define a tuple.
t = 2, 4, 6, 'temp.pdf' # Can skip parenthesis as it is assumed in this context.
Let's see what happens when we try to modify the tuple like we did with a list:
t[1] = -1
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-18-f92606d6fa8b> in <module>() ----> 1 t[1] = -1 TypeError: 'tuple' object does not support item assignment
t.append(0)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-19-78592bf72d62> in <module>() ----> 1 t.append(0) AttributeError: 'tuple' object has no attribute 'append'
del t[1]
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-20-0193a527a912> in <module>() ----> 1 del t[1] TypeError: 'tuple' object doesn't support item deletion
However, we can use the tuple to compose a new tuple:
t = t + (-1.0, -2.0)
print(t)
(2, 4, 6, 'temp.pdf', -1.0, -2.0)
So, why would we use tuples when lists have more functionality?
Step 1: Write a program that prints a table with $t$ values in the first column and the corresponding $y(t) = v_0 t − 0.5gt^2$ values in the second column. Use $n$ uniformly spaced $t$ values throughout the interval [0, $2v_0/g$]. Set $v0 = 1$, $g = 9.81$, and $n = 11$.
Step 2: Once step 1 is working, modify the program so that the $t$ and $y$ values are stored in two lists t and y. Thereafter, transverse the lists with a for loop and write out a nicely formatted table of t and y values using a zip construction.
We have already used many Python functions, e.g. mathematical functions:
from math import *
x = pi
print(cos(x))
-1.0
Other functions you used include len and range:
somelist = range(5, 10, 2)
print(somelist)
print(len(somelist))
range(5, 10, 2) 3
You have also used functions that are executed with the dot syntax (called methods):
C = [5, 10, 40, 45]
i = C.index(10)
C.append(50)
C.insert(2, 20)
A function is a collection of statements we can execute wherever and whenever we want. Functions can take any number of inputs (called arguments) to produce outputs. Functions help to organize programs, make them more understandable, shorter, and easier to extend. For our first example we will turn our temperature conversion code into a function:
def C2F(C):
return (9.0/5)*C + 32
Functions start with def, then the name you want to give your function, then a list of arguments (here C). This is referred to as the function header. Inside the function there is a block of statements called the function body. Notice that the function body is indented - as was the case for the for / while loop the indentation indicates where the function ends. At any point within the function, we can "stop the function" and return as many values/variables as required.
Variables defined within a function are said to have local scope. That is to say that they can only be referenced within that function. Let's consider an example (and look carefully at the indentation!!):
def sumint(start, stop):
s = 0 # variable for accumulating the sum
i = start # counter
while i <= stop:
s += i
i += 1
return s
print(sumint(0, 5))
15
Variables i and s are local variables in sumint. They are destroyed at the end (return) of the function and never visible outside the function (in the calling program); in fact, start and stop are also local variables. So let's see what happens if we now try to print one of these variables:
print(stop)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-28-cdf91c1de162> in <module>() ----> 1 print(stop) NameError: name 'stop' is not defined
Functions can also return multiple values. Let's recycle another of our previous examples - compute $y(t)$ and $y'(t)=v_0-gt$:
def yfunc(t, v0):
g = 9.81
y = v0*t - 0.5*g*t**2
dydt = v0 - g*t
return y, dydt
# call:
position, velocity = yfunc(0.6, 3)
print(position, velocity)
0.034199999999999786 -2.886
Remember that a series of comma separated variables implies a tuple - therefore "return y, dydt" is the same as writing "return (y, dydt)". Therefore, in general what is returned is a tuple. Let's take a look at another example illustrating this:
def f(x):
return x, x**2, x**4
s = f(2)
print(s)
print(type(s)) # The function type() tells us what type a variable it is.
(2, 4, 16) <class 'tuple'>
No return value implies that None is returned. None is a special Python object that represents an ”empty” or undefined value. It is surprisingly useful and we will use it a lot later.
def message(course):
print("%s rocks!"% course)
message("Python")
r = message("Geo")
print("r = ", r)
Python rocks! Geo rocks! r = None
Functions can have arguments of the form variable_name=value and are called keyword arguments:
def somefunc(arg1, arg2, kwarg1=True, kwarg2=0):
print(arg1, arg2, kwarg1, kwarg2)
somefunc("Hello", [1,2]) # Note that we have not specified inputs for kwarg1 and kwarg2
Hello [1, 2] True 0
somefunc("Hello", [1,2], kwarg1="Hi")
Hello [1, 2] Hi 0
somefunc("Hello", [1,2], kwarg2="Hi")
Hello [1, 2] True Hi
somefunc("Hello", [1,2], kwarg2="Hi", kwarg1=6)
Hello [1, 2] 6 Hi
If we use variable_name=value for all arguments, their sequence in the function header can be in any order.
somefunc(kwarg2="Hello", arg1="Hi", kwarg1=6, arg2=[2])
Hi [2] 6 Hello
Let's look at another example - consider a function of $t$, with parameters $A$, $a$, and $\omega$: $$f(t; A,a, \omega) = Ae^{-at}\sin (\omega t)$$. (The choice of equation is actually pretty random - but it serves to show you that it is easy to translate formulae you encounter into Python code). We can implement $f$ in a Python function with $t$ as positional argument and $A$, $a$, and $\omega$ as keyword arguments.
from math import pi, exp, sin
def f(t, A=1, a=1, omega=2*pi):
return A*exp(-a*t)*sin(omega*t)
v1 = f(0.2)
v2 = f(0.2, omega=1)
v2 = f(0.2, 1, 3) # same as f(0.2, A=1, a=3)
v3 = f(0.2, omega=1, A=2.5)
v4 = f(A=5, a=0.1, omega=1, t=1.3)
v5 = f(t=0.2, A=9)
print(v1, v2, v3, v4, v5)
0.778659217806053 0.5219508827258282 0.40664172703834794 4.230480200204721 7.007932960254476
A function can have three types of input and output data:
All output data are returned, all input data are arguments.
Sketch of a general Python function:
Make a Python function gauss( x, m=0, s=1) for computing the Gaussian function $$f(x)=\frac{1}{\sqrt{2\pi}s}\exp\left(-\frac{1}{2} \left(\frac{x-m}{s}\right)^2\right)$$ Call the function and print out the result for x equal to −5, −4.9, −4.8, ..., 4.8, 4.9, 5, using default values for m and s.
Consider this simple example:
def f(x):
if 0 <= x <= pi:
return sin(x)
else:
return 0
print(f(-pi/2), f(pi/2), f(3*pi/2))
0 1.0 0
Sometimes it is clearer to write this as an inline statement:
def f(x):
return (sin(x) if 0 <= x <= pi else 0)
print(f(-pi/2), f(pi/2), f(3*pi/2))
0 1.0 0
In general (the else block can be skipped if there are no statements to be executed when False) we can put together multiple conditions. Only the first condition that is True is executed.
if condition1:
<block of statements, executed if condition1 is True>
elif condition2:
<block of statements>
elif condition3:
<block of statements>
else:
<block of statements>
<next statement of the program>
The following "step" function is known as the Heaviside function and is widely used in mathematics: $$H(x)=\begin{cases}0, & \text{if $x<0$}.\\\\ 1, & \text{if $x\ge 0$}.\end{cases}$$ Write a Python function H(x) that computes H(x).