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