Answer each question on paper first, without running anything. Then open the answer. If you got it wrong, run the code in your workspace and find the line where your prediction and Python part ways.
Warm-up
Warm-up 1
Calls inside calls are read from the inside out. Two ways to round a fee to cents. What does the program print?
fee = -2.675
print(round(abs(fee), 2), round(abs(fee) * 100) / 100)
2.68 2.682.67 2.682.67 2.67-2.67 -2.68
Show the answer
2.67 2.68
Inside out. abs gives 2.675, which is stored slightly below 2.675, so round(..., 2) gives 2.67. But 2.675 * 100 comes out as exactly 267.5, round gives 268, and 268 / 100 is 2.68. Same fee, two answers: session 2's floats again.
Warm-up 2
A function returns two values. What does the program print?
def low_high(prices):
return min(prices), max(prices)
result = low_high([190.5, 187.2, 193.8])
low, high = low_high([190.5, 187.2, 193.8])
print(type(result).__name__, high)
TypeError on line 4list 193.8tuple 193.8tuple 187.2
Show the answer
tuple 193.8
return a, b gives back ONE value: a tuple. Unpacking with two names on the left takes it apart.
Warm-up 3
The same function, called with keywords. What does the program print?
def value(shares, price):
return shares * price
print(value(price=190.5, shares=10), value(190.5, 10))
1905.0 1905.0TypeError on line 41905.0 TypeError1905.0 19.05
Show the answer
1905.0 1905.0
Keywords are matched by name, so their order does not matter. The second call swaps the positions, and here it still prints 1905.0 only because multiplication does not care. With a division it would lie.
Warm-up 4
A method belongs to a type. What happens?
tickers = "aapl,nesn".split(",")
print(tickers.upper())
AAPL,NESN['AAPL', 'NESN']TypeError on line 2AttributeError on line 2
Show the answer
AttributeError on line 2
.split() gives a list, and .upper() is a method of str, not of list. Use [t.upper() for t in tickers] later, or a loop now.
Warm-up 5
Two random numbers from the same seed. What does the program print?
import random
random.seed(1)
a = random.random()
random.seed(1)
b = random.random()
print(a == b)
It changes every runNameError on line 3TrueFalse
Show the answer
True
A seed fixes the starting point, so the same seed gives the same numbers in the same order. That is how every demo in this course gives the same result every run.
Warm-up 6
Two names for one object. What does the program print?
class Position:
def __init__(self, ticker, shares):
self.ticker = ticker
self.shares = shares
a = Position("AAPL", 10)
b = a
b.shares = 12
print(a.shares)
12NoneAttributeError on line 910
Show the answer
12
b = a does not copy the object; both names point at the same Position, like session 3's two names for one list.
Reading tasks
Every task starts from this ledger:
closes = [190.5, 187.2, 193.8, 185.1, 191.4, 198.6, 194.0] # AAPL, day 0 to day 6
Task 1 · print is not return
Two functions that look almost the same. Predict EVERYTHING the program shows, line by line, in order. Then say what a and b hold.
def value_p(shares, price):
print(shares * price)
def value_r(shares, price):
return shares * price
a = value_p(4, 102.3)
b = value_r(4, 102.3)
print(a, b)
What does the last line print?
Task 1: 409.2 NoneTask 1: None NoneTask 1: 409.2 409.2Task 1: None 409.2
Show the answer
Task 1: None 409.2
value_p SHOWS 409.2 on the screen while it runs, then gives back nothing: every function without return returns None, so a is None. value_r shows nothing and hands 409.2 back to the program. A value on the screen is not a value in a variable (session 1's round, again).
Task 2 · two variables called total
Predict the print. Then explain: the function assigns to total. Why is the total outside still 100.0?
total = 100.0
def add_fee(amount):
total = amount + 2.5
return total
x = add_fee(40.0)
print(x, total)
What does the print show?
42.5 42.5102.5 100.042.5 100.0NameError
Show the answer
42.5 100.0
Assigning inside a function creates a LOCAL name. The total inside add_fee is a different variable that happens to share the name; it disappears when the call ends. The only way out of a function is return.
Task 3 · what the function changed
Two functions, each changes its parameter. Predict both prints. Why does the list outside change, and the number outside not?
def add_position(shares, n):
shares.append(n)
return len(shares)
def add_one(n):
n = n + 1
return n
shares = [10, 4, 2]
k = add_position(shares, 7)
print(k, shares)
m = 5
y = add_one(m)
print(m, y)
What does Task 3a print?
None [10, 4, 2, 7]3 [10, 4, 2]4 [10, 4, 2, 7]4 [10, 4, 2]
Show the answer
4 [10, 4, 2, 7]
Inside add_position, shares is a second name for the caller's list (session 3: two names, one list), and append changes that list in place. In add_one, n = n + 1 builds a NEW number and moves the local label n to it; the caller's 5 cannot change, because ints are immutable. Lists are mutable.
Task 4 · which methods change the value?
One string method chain, one list method. Predict all four printed values. Which method changed its object, and which returned a new one?
ticker = " nesn "
t2 = ticker.strip().upper()
shares = [10, 4, 2]
s2 = shares.sort()
print(repr(ticker), t2, shares, s2)
What does the print show?
'NESN' NESN [2, 4, 10] [2, 4, 10]'NESN' NESN [2, 4, 10] None' nesn ' NESN [2, 4, 10] None' nesn ' NESN [10, 4, 2] [2, 4, 10]
Show the answer
' nesn ' NESN [2, 4, 10] None
Strings never change: .strip() and .upper() each return a NEW string, and ticker keeps its spaces. .sort() changes the list itself and returns None, so s2 is None. That is why shares = shares.sort() destroys a list. repr() shows the quotes, so the spaces become visible.
Task 5 · the weight that lies
AAPL is worth 1905.0 of a 3338.2 CHF ledger. The function below computes its weight in percent. The call runs and prints a number. Is it right? Find the mistake and fix it so that no one can make it again.
def weight(value, total):
return value / total * 100
print(f"{weight(3338.2, 1905.0):.1f}%")
What does the buggy call print?
TypeError1.8%57.1%175.2%
Show the answer
175.2%
The buggy call prints 175.2%: arguments are matched by POSITION, so value got the total and total got the value. No error, because dividing two numbers always works. Rough check: one position cannot be 175 % of the ledger. Keyword arguments (value=..., total=...) make the order impossible to get wrong.
Self-check (home questionnaire)
Check 1
What does this print?
def double(x):
x * 2
print(double(3))
6None3x * 2
Show the answer
None
Check 2
What does this print?
def diff(a, b=2):
return a - b
print(diff(5), diff(b=1, a=5))
5 43 4TypeError3 -4
Show the answer
3 4
Check 3
What does this print?
total = 10
def reset():
total = 1
return total
reset()
print(total)
NameError1None10
Show the answer
10
Check 4
What does this print?
x = [3, 1, 2].sort(); print(x)
[1, 2, 3][3, 1, 2]AttributeErrorNone
Show the answer
None
Check 5
What does this print?
print(" nesn ".strip().upper().replace("N", "M"))
MESMMESN MESM NESN
Show the answer
MESM
Check 6
What does this print?
import math; print(math.ceil(2.1), math.floor(-2.1))
2 -22 -33 -33 -2
Show the answer
3 -3
Check 7
What does this print?
def add_one(xs):
xs.append(1)
a = []
add_one(a)
add_one(a)
print(a)
[1][1, 1][]None
Show the answer
[1, 1]
Check 8
What does this print?
class Position:
def __init__(self, ticker, shares, price):
self.ticker = ticker
self.shares = shares
self.price = price
def value(self):
return self.shares * self.price
p = Position("NESN", 2, 5.0)
p.shares += 1
print(p.value())
3AttributeError15.010.0
Show the answer
15.0
Check 9
What does this print?
import random; random.seed(1); a = random.randint(1, 6); random.seed(1); b = random.randint(1, 6); print(a == b)
NoneTrueIt depends on the runFalse
Show the answer
True
Check 10
In numpy, arr.shape and arr.sum() differ because…
There is no difference; the parentheses are optionalshape is an attribute (a stored fact); sum() is a method (an operation you call)shape is a function from the math modulesum() is an attribute; shape is a method
Show the answer
shape is an attribute (a stored fact); sum() is a method (an operation you call)