Data Science and Advanced Programming · Session 4

Session 4: the Wooclap questions

The reading questions from class, to redo at home.

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)
  1. 2.68 2.68
  2. 2.67 2.68
  3. 2.67 2.67
  4. -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)
  1. TypeError on line 4
  2. list 193.8
  3. tuple 193.8
  4. tuple 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))
  1. 1905.0 1905.0
  2. TypeError on line 4
  3. 1905.0 TypeError
  4. 1905.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())
  1. AAPL,NESN
  2. ['AAPL', 'NESN']
  3. TypeError on line 2
  4. AttributeError 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)
  1. It changes every run
  2. NameError on line 3
  3. True
  4. False
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)
  1. 12
  2. None
  3. AttributeError on line 9
  4. 10
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?

  1. Task 1: 409.2 None
  2. Task 1: None None
  3. Task 1: 409.2 409.2
  4. Task 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?

  1. 42.5 42.5
  2. 102.5 100.0
  3. 42.5 100.0
  4. NameError
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?

  1. None [10, 4, 2, 7]
  2. 3 [10, 4, 2]
  3. 4 [10, 4, 2, 7]
  4. 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?

  1. 'NESN' NESN [2, 4, 10] [2, 4, 10]
  2. 'NESN' NESN [2, 4, 10] None
  3. ' nesn ' NESN [2, 4, 10] None
  4. ' 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?

  1. TypeError
  2. 1.8%
  3. 57.1%
  4. 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))
  1. 6
  2. None
  3. 3
  4. x * 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))
  1. 5 4
  2. 3 4
  3. TypeError
  4. 3 -4
Show the answer

3 4

Check 3

What does this print?

total = 10
def reset():
    total = 1
    return total
reset()
print(total)
  1. NameError
  2. 1
  3. None
  4. 10
Show the answer

10

Check 4

What does this print?

x = [3, 1, 2].sort(); print(x)
  1. [1, 2, 3]
  2. [3, 1, 2]
  3. AttributeError
  4. None
Show the answer

None

Check 5

What does this print?

print(" nesn ".strip().upper().replace("N", "M"))
  1. MESM
  2. MESN
  3. MESM
  4. NESN
Show the answer

MESM

Check 6

What does this print?

import math; print(math.ceil(2.1), math.floor(-2.1))
  1. 2 -2
  2. 2 -3
  3. 3 -3
  4. 3 -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]
  2. [1, 1]
  3. []
  4. 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())
  1. 3
  2. AttributeError
  3. 15.0
  4. 10.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)
  1. None
  2. True
  3. It depends on the run
  4. False
Show the answer

True

Check 10

In numpy, arr.shape and arr.sum() differ because…

  1. There is no difference; the parentheses are optional
  2. shape is an attribute (a stored fact); sum() is a method (an operation you call)
  3. shape is a function from the math module
  4. sum() 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)