Python review#
key concepts: everything has a type, assigning is binding, scope of variable
Type#
In python, everything has a type. You can check the type of a variable using the type()
function.
type(1)
int
type([1,2,3])
list
Operations#
# This is true division, which always returns a float
print(3/4)
# This is floor division, which truncates the decimal without rounding
print(3//4)
# This is the modulo operator, which returns the remainder of the division
print(3%4)
# This is exponentiation/raising to a power
print(3**2)
0.75
0
3
9
Floating Point Numbers#
0.8 - 0.7 == 0.1
False
This is false because of round-off error
We can’t check for equality (in the mathematical sense) of floating point numbers. Instead, we can check if the difference between the two numbers is less than a small tolerance value.
a = 0.8 - 0.7
b = 0.1
abs(a-b)<1e-12
True
Loop#
# range(start[default=0], stop, step[default=1])
for i in range(2,9,3):
print(i)
2
5
8
x = [1, 2, 3.2, True, "test"]
for elem in x:
print(elem)
1
2
3.2
True
test
x = 2
while x < 10:
x = x*2
print(x)
4
8
16
List comprehensions#
# Let's create a list of numbers from 1 to 100 that are divisible by 7
x = []
for i in range(1,101):
if i % 7 == 0:
x.append(i)
print(x)
[7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]
# List comprehension is a more concise way to create the previous list
x = [i for i in range(1, 101) if i % 7 == 0]
print(x)
[7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]
# Another example of list comprehension using if-else
x = [-2, 1, 3, -4, 5]
# we can compute the following list, where all negative numbers in x are replaced by 0
y = [elem if elem > 0 else 0 for elem in x]
print(y)
[0, 1, 3, 0, 5]
Assignment#
Assignment = statements in Python do not copy objects, they create bindings between a variable and an object.
This is a very important concept in python, especially when dealing with mutable objects (lists, dictionaries, etc.)
# normal behavior
a=1
b=a
b=2
print(a,b)
1 2
a = [1,2,3]
b = a
a[0]=-1
print(a,b)
# a and b bind to the same list
# essentially, different name for the same thing
# change one, change the other
[-1, 2, 3] [-1, 2, 3]
# change b, a also changed
b[1]=-2
print(a,b)
[-1, -2, 3] [-1, -2, 3]
a = [1,2,3]
b = []
# at this stage, a and b are different lists,
for i in a:
b.append(i)
a[0] = -1
print(a,b)
# change one have no effect on the other
[-1, 2, 3] [1, 2, 3]
a = [1,2,3]
b = a + [4] # this is create a new list by concatenating two list
# at this stage, a and b are different lists,
a.append(4) # this is modifying the list in a
print(a,b)
a[0] = -1
print(a,b)
[1, 2, 3, 4] [1, 2, 3, 4]
[-1, 2, 3, 4] [1, 2, 3, 4]
We can modify a list in-place using a function
This is consistent with our notion of “assigning is binding”
# this function do not have return, by default, it returns None
def changelist_noreturn(x):
x[0] = -1
z = [1,2,3]
y = changelist_noreturn(z)
print(z,y)
# z is modified in-place
[-1, 2, 3] None
# we can modify a list inplace using a function
def changelist_withreturn(x):
x[0] = -1
return x
z = [1,2,3]
y = changelist_withreturn(z)
# inside the function, x is bind to the same list as z
# then we assign x to y, so y also bind to the same list
print(z,y)
z[-1] = -3
print(z,y)
[-1, 2, 3] [-1, 2, 3]
[-1, 2, -3] [-1, 2, -3]
Scope of variables#
If the function does not find the local variable, it will try to find it in an “upper” scope
If the function do not have a return
, then None
is returned
# normal behavior: local variable a = 1
# does not modify global variable a
a = 2
def addone(x):
a = 1
b = x + a
return b
c = addone(a)
print(c,a)
3 2
a = 2
def addone2(x):
b = x + a
# didn't find a locally, so try to find it globally
# in matlab, this will throw an error
return b
c = addone2(1)
print(c)
3
a = 10
b = addone2(2)
# always try to use the value of a in the workspace
# this has nothing to do with order of the cell
print(a,b)
10 12
a = 2
def change_a(x):
a = x
# a is now a local variable, it does not modify global variable a
# this is different from addone2: we are not looking for a, we are define a locally
return a
y = change_a(10)
print(a,y)
2 10
# you can pass function as argument to other function
# we have something similar when implementing the bisection method using matlab
def cubic(x):
return x**3 - 1
def evaluate(f, x):
# evaluate function f at x
return f(x)
evaluate(cubic,3)
26
Lambda function#
A lambda function is a handy way to define a short function in a single line.
It it the same as defining a function using def
.
f = lambda x:x+2
type(f)
function
Sorting#
The sorted()
function returns a new sorted list
The .sort()
method of a list sorts the list in place.
Use the reverse
argument to sort in descending order.
Use the key
argument to specify a function to be called on each list element prior to making comparisons.
a = [5,2,3,1,4]
c = a.sort()
print(a,c)
# a is modified in place
# c is None, because sort() does not return anything
[1, 2, 3, 4, 5] None
a = [5,2,3,1,4]
b = sorted(a)
print(a,b)
# a is not modified
# b is a new sorted list
[5, 2, 3, 1, 4] [1, 2, 3, 4, 5]
# Sort by decreasing order of the absolute value of the elements
c = [5, -2, 3, -1, 4]
sorted(c, reverse = True, key = lambda x:abs(x))
[5, 4, 3, -2, -1]
Extra material#
Not on the exam, but might be useful for coding.
# A function can call itself, this is called recursion
# the function compute n-factorial: myfac(n) = n! = n * (n-1) * (n-2) ... * 2 * 1
def myfac(n):
if n == 1:
return 1
else:
return n*myfac(n-1)
print(myfac(2))
print(myfac(4))
2
24
# for actual copy of the data, look up python deepcopy
from copy import deepcopy
a = [1,2,3]
b = deepcopy(a)
a[0] = -1
print(a,b)
[-1, 2, 3] [1, 2, 3]
# How to see if two variables are bound to the same object?
# Can use id(), think of it as the memory address of the object
a = [1,2,3]
b = a
id(a), id(b) # same memory address
(4518811712, 4518811712)
# The notion of "scope" can be more complicated,
# for simplicity, we only talked about global and local scope.
# When your function return a function, it is called a closure, this function "capture the environment variable"
# No need to worry about this for this class.
def function_factory(a):
# This function returns another function
def myadd(x):
return x + a
return myadd
# Generate a function with 'a' captured at the value 1
myadd_1 = function_factory(1)
# Generate another function with 'a' captured at the value 20
myadd_20 = function_factory(20)
# Even if we change 'a' now, it does not affect the closures
a = 99
print(myadd_1(1))
print(myadd_20(1))
2
21