Quick refresher. What does f (without parens) refer to?
def f(x):
return x + 1
print(f) # <function f at 0x...>
print(f(3)) # 4f is the function itself; f(3) is calling it.
Right. And because f is just a value — like 5 or "hello" — you can do everything with it that you'd do with any other value. Pass it to other functions. Return it. Store it in a list:
def add(a, b):
return a + b
def apply(fn, x, y):
return fn(x, y)
print(apply(add, 3, 4)) # 7apply takes add as an argument and calls it inside?
Exactly. fn is a parameter; inside the body, fn(x, y) calls whatever was passed in. You could pass add, or mul, or any 2-argument function — the receiver doesn't care which one, only that it can be called with two values.
When does this matter?
Constantly. sorted(items, key=lambda x: x.value) — the key argument is a function. map(f, list) — first argument is a function. Decorators (tomorrow) are functions that take other functions and return new ones. The whole pattern of "pass the behaviour in as a parameter" depends on functions being first-class values.
A function defined with def is just a value bound to a name — same as x = 5. You can do everything with it that you'd do with any value:
def add(a, b):
return a + b
f = add
print(f(3, 4)) # 7f and add now point to the same function object.
def apply(fn, x, y):
return fn(x, y)
print(apply(add, 3, 4)) # 7def add(a, b): return a + b
def mul(a, b): return a * b
ops = {"add": add, "mul": mul}
print(ops["add"](3, 4)) # 7
print(ops["mul"](3, 4)) # 12def make_adder(n):
def adder(x):
return x + n
return adder
add5 = make_adder(5)
print(add5(3)) # 8The inner function adder closes over n — it remembers the n=5 from when make_adder was called. This is a closure — central to how decorators work.
| Expression | Type | What it is |
|---|---|---|
add | function | the function object — first class |
add(3, 4) | int | result of calling add with 3, 4 |
add() | TypeError | call with wrong number of args |
Getting these confused is one of the most common Python mistakes:
result = add(3, 4) # what you wanted
result = add # the function itself, not its resultlambda — anonymous functionsFor short one-expression functions, lambda saves a def:
square = lambda x: x * x
print(square(5)) # 25
# Inline use — common with sort/map/filter
items = [("a", 3), ("b", 1), ("c", 2)]
items.sort(key=lambda pair: pair[1])
print(items) # [('b', 1), ('c', 2), ('a', 3)]Use lambda for short, throwaway functions. For anything multi-line or named, use def — easier to debug, easier to test.
Everything in week 2 — decorators, generators (kind of), context managers — depends on functions being first-class. You can't write a decorator without passing a function to a function and getting a new function back. Get comfortable with the shape first.
Quick refresher. What does f (without parens) refer to?
def f(x):
return x + 1
print(f) # <function f at 0x...>
print(f(3)) # 4f is the function itself; f(3) is calling it.
Right. And because f is just a value — like 5 or "hello" — you can do everything with it that you'd do with any other value. Pass it to other functions. Return it. Store it in a list:
def add(a, b):
return a + b
def apply(fn, x, y):
return fn(x, y)
print(apply(add, 3, 4)) # 7apply takes add as an argument and calls it inside?
Exactly. fn is a parameter; inside the body, fn(x, y) calls whatever was passed in. You could pass add, or mul, or any 2-argument function — the receiver doesn't care which one, only that it can be called with two values.
When does this matter?
Constantly. sorted(items, key=lambda x: x.value) — the key argument is a function. map(f, list) — first argument is a function. Decorators (tomorrow) are functions that take other functions and return new ones. The whole pattern of "pass the behaviour in as a parameter" depends on functions being first-class values.
A function defined with def is just a value bound to a name — same as x = 5. You can do everything with it that you'd do with any value:
def add(a, b):
return a + b
f = add
print(f(3, 4)) # 7f and add now point to the same function object.
def apply(fn, x, y):
return fn(x, y)
print(apply(add, 3, 4)) # 7def add(a, b): return a + b
def mul(a, b): return a * b
ops = {"add": add, "mul": mul}
print(ops["add"](3, 4)) # 7
print(ops["mul"](3, 4)) # 12def make_adder(n):
def adder(x):
return x + n
return adder
add5 = make_adder(5)
print(add5(3)) # 8The inner function adder closes over n — it remembers the n=5 from when make_adder was called. This is a closure — central to how decorators work.
| Expression | Type | What it is |
|---|---|---|
add | function | the function object — first class |
add(3, 4) | int | result of calling add with 3, 4 |
add() | TypeError | call with wrong number of args |
Getting these confused is one of the most common Python mistakes:
result = add(3, 4) # what you wanted
result = add # the function itself, not its resultlambda — anonymous functionsFor short one-expression functions, lambda saves a def:
square = lambda x: x * x
print(square(5)) # 25
# Inline use — common with sort/map/filter
items = [("a", 3), ("b", 1), ("c", 2)]
items.sort(key=lambda pair: pair[1])
print(items) # [('b', 1), ('c', 2), ('a', 3)]Use lambda for short, throwaway functions. For anything multi-line or named, use def — easier to debug, easier to test.
Everything in week 2 — decorators, generators (kind of), context managers — depends on functions being first-class. You can't write a decorator without passing a function to a function and getting a new function back. Get comfortable with the shape first.
Create a free account to get started. Paid plans unlock all tracks.