Exercise Answers¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Meta-Programming"]
section["Function Wrappers Transparent Decorators"]
page["Exercise Answers"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
orient["Orient on the page map"] --> read["Read the main claim and examples"]
read --> inspect["Inspect the related code, proof, or capstone surface"]
inspect --> verify["Run or review the verification path"]
verify --> apply["Apply the idea back to the module and capstone"]
Use this page after attempting the exercises yourself. The goal is not to match every example literally. The goal is to compare your reasoning against answers that distinguish mechanics, transparency, and policy honestly.
Answer 1: Build one wrapper skeleton by hand¶
Example answer:
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
def greet(name):
return f"Hello, {name}"
greet_wrapped = log_calls(greet)
Strong explanation:
wrappercloses over the originalgreetfunction throughfunc- the manual rebinding step makes the mechanics explicit before
@syntax hides it
Good conclusion:
The decorator is just a higher-order function that returns a nested wrapper carrying the original callable in its closure.
Answer 2: Desugar one stacked decorator example¶
Example answer:
desugars to:
Strong explanation:
- definition-time application is bottom-up
- call-time execution starts at the outermost wrapper and flows inward
Good conclusion:
Stacked decorators are never only formatting. Their order changes composition and therefore changes behavior.
Answer 3: Implement one thin practical wrapper¶
Example answer:
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
print("elapsed:", time.perf_counter() - start)
return wrapper
Strong explanation:
- the wrapper adds one narrow behavior: timing
- it still returns the original result
- it still lets exceptions propagate, while reporting timing in
finally
Good conclusion:
This is a thin wrapper because the added concern is narrow and does not take ownership of cross-call policy or hidden state.
Answer 4: Show semantic drift in a stateful wrapper¶
Example answer:
import functools
def once(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not hasattr(wrapper, "_once_result"):
wrapper._once_result = func(*args, **kwargs)
return wrapper._once_result
return wrapper
Strong explanation:
- the wrapper stores
_once_resulton itself - later calls return the first cached result even if new arguments are supplied
Good conclusion:
This is a policy surface, not only a thin wrapper, because the wrapper now controls behavior across calls through stored state.
Answer 5: Preserve wrapped identity honestly¶
Example answer:
import functools
import inspect
def bare(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def preserved(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Strong evidence:
- the bare version often reports
__name__ == "wrapper" - the preserved version reports the original name and docstring
- the preserved version has
__wrapped__ inspect.signatureis much more likely to reflect the logical callable correctly
Good conclusion:
functools.wraps is part of correctness because it preserves the inspection and review
surface that later tools depend on.
Answer 6: Review a small cache or retry decorator¶
Example answer:
Suppose the decorator stores cached results on wrapper attributes and exposes
cache_clear().
Strong assessment:
- the wrapper owns state across calls
- reset behavior must be explicit
- limitations such as hashability or concurrency need to be documented
Good conclusion:
Once a decorator starts caching or retrying, it has moved beyond a thin wrapper and should be reviewed as a small policy system rather than as harmless sugar.
What strong Module 04 answers have in common¶
Across the whole set, strong answers share the same habits:
- they separate definition-time rebinding from call-time behavior
- they distinguish thin wrappers from stateful policy
- they preserve callable identity when transparency is claimed
- they name limits instead of hiding them behind decorator syntax
If an answer still sounds like "decorators just add behavior," revise it until you can say what changed, when it changed, and what still remains visible to tools and reviewers.