PART 2 · PYTHON FOUNDATIONS · CHAPTER 6
Functions, scope and closures
FreeLesson 29 of 78
Pipelines are built out of small functions. Know how arguments bind, where names are looked up, and what a closure keeps alive.
def load(path, *columns, sep=",", **options):
print(path, columns, sep, options)
load("orders.csv")
load("orders.csv", "order_id", "amount", sep="|", skip=1)
def area(*, width, height): # keyword-only
return width * height
print(area(width=3, height=4))
def summary(values):
return min(values), max(values), sum(values) / len(values)
lo, hi, avg = summary([747, 3498, 1048])
print(lo, hi, round(avg, 1))Output
orders.csv () , {}
orders.csv ('order_id', 'amount') | {'skip': 1}
12
747 3498 1764.3*columns collects extra positional arguments into a tuple, **options collects extra keywords into a dict. A bare * forces the rest to be passed by name, which stops mixups like area(4, 3).
count = 0
def bump():
count += 1 # assigning makes it local
bump()Output
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
Assigning to a name anywhere inside a function makes it local for the whole function. Python looks names up in order: Local, Enclosing, Global, Built-in (LEGB).
def make_counter():
count = 0
def bump():
nonlocal count # closure keeps it alive
count += 1
return count
return bump
c = make_counter()
print(c(), c(), c())Output
1 2 3
fns = [lambda: i for i in range(3)]
print([f() for f in fns]) # late binding
fns = [lambda i=i: i for i in range(3)]
print([f() for f in fns]) # bind now
steps = [str.strip, str.lower, lambda s: s.replace(" ", "_")]
value = " Order Date "
for step in steps: # functions are values
value = step(value)
print(value)Output
[2, 2, 2] [0, 1, 2] order_date
STATUS_MAP = {"delivered": "delivered", "dlvd": "delivered",
"cancelled": "cancelled", "canceled": "cancelled"}
def normalize_status(raw: str | None) -> str:
"Map messy source statuses to one vocabulary."
if not raw:
return "unknown"
return STATUS_MAP.get(raw.strip().lower(), "unknown")
assert normalize_status(" DLVD ") == "delivered"
assert normalize_status(None) == "unknown"
print([normalize_status(s) for s in ["Canceled", "shipped", ""]])
print(normalize_status.__doc__)Output
['cancelled', 'unknown', 'unknown'] Map messy source statuses to one vocabulary.
Practice before you move on
- Write a closure make_counter() that returns a function counting how many times it has been called.
- Fix the late binding example so that each function keeps its own value.