Lesson 14 of 20 · 52 min · UG / professional
Decorators: wrappers you can see
@timer is syntax for a function that calls another function. Class decorators wrap a whole type. If you cannot write wrap(fn), you are not ready to decorate.
PASS / FAIL card
Expected vs actual is a test.
You will be able to
- ✓ Wrap a call with start/end logs without changing the inner spec
- ✓ Keep the inner function testable without the wrapper
- ✓ See @timer as syntax for wrap(fn)
Before the slow report, say start and end
That wrapper is the idea behind decorators. Here it is wrap calling percent — visible, testable, no mystery. On a laptop, @timer above def report(): is syntax that builds the same wrapping. If you cannot write wrap(fn), you are not ready to decorate.
The inner function stays pure: percent returns a number. The wrapper logs. Tests call percent directly. Ops look at INFO lines. Mixing them is how a unit test needs a log file.
Wrappers you actually want
Timing a slow attendance CSV. Checking that the caller is allowed to run a job. Retrying a weather GET a bounded number of times. Each is a wrapper. None of them should change the meaning of the inner result — only observe or decide whether to call.
functools.wraps keeps the inner name in traces. Class decorators wrap a whole type — same idea, bigger surface. Copy the sample; do not paste @ into this Run box.
Words that matter
- Wrapper
- A function that calls another, adding logging, timing or checks.
- Decorator
- Laptop syntax (@name) that applies a wrapper.
- Inner function
- The job itself — still testable alone.
Common mistakes
Avoid: A decorator that changes the return value ‘a bit’ with no test.
Do this: Wrapper observes; inner result stays the spec.
Run it step by step
Each box is a real program. Press Run, change a number, Run again — the output must follow your code.
1. Step 1 — inner stays simple
Tests would call percent directly.
Edit the example, press Run, then Build if you want a compile check.
build
Press Build to compile.
2. Step 2 — wrap() calling percent()
Start/end are extra. 90 is still the spec.
Edit the example, press Run, then Build if you want a compile check.
build
Press Build to compile.
On a full Python install — functools.wraps + a timer
Keep the inner name in traces. Time utilisation without editing percent’s body.
No pip — stdlib.
Decorator form of the same wrap() you will run in the sandbox.
Real library code (not run in this browser sandbox)
from functools import wraps
from time import perf_counter
def timed(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
t0 = perf_counter()
out = fn(*args, **kwargs)
print("INFO ms", round((perf_counter() - t0) * 1000, 1))
return out
return wrapper
@timed
def percent(used, seats):
return used * 100 / seats
Example program — Check, then call
A guard wrapper. Inner percent never sees seats 0.
Edit the example, press Run, then Build if you want a compile check.
build
Press Build to compile.
Your turn — Wrap with INFO
Call a function that returns 90. Print INFO and 90.
Edit the example, press Run, then Build if you want a compile check.
build
Press Build to compile.
Self-assessment
Check your understanding before you mark the lesson complete.
Progress is stored in a browser cookie on this device.