Functions · Lesson 11 of 18 · 20 min
Closures and decorators
Functions that remember, wrap, and rewrite other functions.
- Return inner functions that close over state
- Write a decorator with functools.wraps
- See late-binding of loop variables
A closure keeps enclosing names alive
When an inner function refers to a name from its enclosing function, Python stores that binding on the function's `__closure__`. The inner function can be returned and called later; the enclosing locals still exist for it.
This is how you manufacture specialized functions without a class: a factory that takes a multiplier and returns a function that uses it.
def multiply_by(n):
def inner(x):
return n * x
return inner
triple = multiply_by(3)
print(triple(10))
print(triple.__closure__[0].cell_contents)Decorators are syntactic sugar
`@deco` above `def f():` is `f = deco(f)`. A decorator takes a function and returns a callable, usually a wrapper that runs extra code before or after the original. Stacked decorators apply nearest-first: `@a` then `@b` then `def f` is `f = a(b(f))`.
Always copy metadata with `@functools.wraps(fn)` on the wrapper so `__name__`, `__doc__`, and tools like `inspect.signature` still describe the original.
from functools import wraps
def traced(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
print(f"{fn.__name__}{args} -> {result}")
return result
return wrapper
@traced
def add(a, b):
return a + b
add(2, 3)
print(add.__name__)Check yourself
Quiz
1.`@deco` on `def f` is equivalent to:
2.Why use functools.wraps?
3.`[lambda: i for i in range(3)][0]()` typically returns:
Practice
Exercise
Write `make_adder(n)` that returns a function adding n to its argument. Write `once(fn)` that returns a wrapper which calls fn only the first time and returns that result forever after.
Tutor
Ask a question about this lesson. Answers are generated on demand and capped so the session stays focused.