print(round(2 * 4.50, 2)) # 2 wraps
print(round(1 * 12.00, 2)) # 1 bowl
print(round(5 * 3.20, 2)) # 5 fries9.0
12.0
16.0
Programming with Python
Pens down. The checkpoint is behind you. Now the reason we’re all here.
. . .
Kevin has been “reusing” code the only way he knows how: he pasted the same receipt block 14 times, once per menu item. A ten-cent price change last week cost him a whole afternoon of hunting down copies.
. . .
Today the code learns to reuse itself. We meet the function, and build a first class.
Kevin totals each receipt line by hand, the same shape, over and over:
print(round(2 * 4.50, 2)) # 2 wraps
print(round(1 * 12.00, 2)) # 1 bowl
print(round(5 * 3.20, 2)) # 5 fries9.0
12.0
16.0
. . .
Same calculation, retyped every time. Change the rule once and you must fix it everywhere. There is a better way.
def: name the calculation onceWrite the calculation once, give it a name, and call it whenever you need it:
def line_total(qty, price): # def NAME(inputs):
return round(qty * price, 2) # indented body, hands a value back
print(line_total(2, 4.50))9.0
. . .
def starts the definition · the name is line_total · the block is indented · return hands the answer back.
qty, price2, 4.50def line_total(qty, price): # qty and price are PARAMETERS
return round(qty * price, 2)
print(line_total(3, 3.20)) # 3 and 3.20 are ARGUMENTS9.6
. . .
Same function, different arguments, different result. No retyping.
return: hand the value backreturn sends a value back to whoever called the function, so you can store it and use it later:
def line_total(qty, price):
return round(qty * price, 2)
subtotal = line_total(3, 3.20) # catch what came back
print(subtotal)
print(subtotal + 1.50) # ...and keep using it9.6
11.1
label_price prints but has no return. What does the last line show?
def label_price(price):
print(f"{price:.2f} EUR")
result = label_price(8.50)
print(result)a) 8.50 EUR b) Error c) None
. . .
Predict first. Commit to an answer before the next slide.
print shows, return hands backc) None — label_price prints its line while running, but with no return it hands back None. That None is what lands in result. Printing is not returning.
def label_price(price):
print(f"{price:.2f} EUR")
result = label_price(8.50) # prints while running...
print(result) # ...but the value handed back is None8.50 EUR
None
A parameter can carry a default, used when the caller leaves it out:
def service_fee(total, rate=0.05): # rate defaults to 5 %
return round(total * rate, 2)
print(service_fee(80)) # default rate → 4.0
print(service_fee(80, 0.10)) # override it → 8.04.0
8.0
. . .
Defaults let one function cover the common case and the special case.
A function can use another function you already wrote — reuse builds on reuse:
def bill(qty, price):
items = line_total(qty, price) # reuse line_total
return round(items + service_fee(items), 2) # ...and service_fee
print(bill(2, 4.50))9.45
. . .
line_total and service_fee do their jobs; bill just orchestrates. That’s how small pieces become a program.
A parameter is the function’s own private copy. What happens inside stays inside:
def bump(n):
n = n + 10 # changes the function's OWN copy of n
return n
print(bump(3)) # 13 — the returned value13
. . .
Names created inside a function live in its scope. They vanish when the function ends.
bump adds ten to its parameter. We call it, then print stock. What does the last line print?
def bump(n):
n = n + 10
return n
stock = 3
bump(stock)
print(stock)a) 3 b) 13 c) Error
. . .
Predict first. Commit to an answer before the next slide.
a) 3 — bump changes only its own copy n. We never stored what it returned, so stock out here never moves. The function cannot reach out and rewrite your variables.
def bump(n):
n = n + 10
return n
stock = 3
bump(stock) # the return value is thrown away
print(stock) # still 33
stock = bump(stock)). Deliberate, visible. . .
Isolation is what makes functions safe to reuse. That is the whole point of this episode.
Sometimes data and the things you do with it belong together. A class is a blueprint that bundles both:
. . .
A method is just a function that lives inside a class and can read the object’s own data.
class, __init__, and selfclass Delivery:
def __init__(self, courier, distance_km): # runs when you build one
self.courier = courier # store data ON the object
self.distance_km = distance_km
self.rate_per_km = 1.20 # every delivery carries its rate
def fee(self): # a method — note self
return round(self.distance_km * self.rate_per_km, 2). . .
__init__ sets up a new object · self is this object · attributes are stored on self and read back through self. fee() multiplies two stored attributes.
Instantiate the class to make an object, then use its data and its methods:
trip = Delivery("Nadia", 4) # build one — __init__ runs
print(trip.courier) # read stored data
print(trip.fee()) # call the methodNadia
4.8
. . .
trip is one Delivery object. trip.fee() computes from its own stored distance: data and behavior, traveling together.
__init__ and one method. That’s itself confusion), and that is not this course. . .
If the self keyword feels odd right now, that’s completely normal. Copy the shape from the worked example. The intuition follows the practice.
return, give a parameter a default, and build the startup’s first Order class, ending in the Tip Calculator Championship. . .
Download your .py before you leave. Closing the tab without downloading loses your work, and downloading is exactly how you just handed in the checkpoint.
def, takes parameters, and hands a value back with return: print shows, return gives back (no return → None)self, via __init__) with behavior (methods). You’ll write just one small one today. . .
Next time — Episode 4: the menu outgrows Kevin’s seventeen loose variables (price1, price2, price_final_FINAL2), and nobody can find anything. We give the data a shape: lists and dictionaries.
. . .
Nothing new here, but these are still great books to start with!
. . .
For more, see the literature list of this course.