Nested Functions and Wrapper Skeletons¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Meta-Programming"]
section["Function Wrappers Transparent Decorators"]
page["Nested Functions and Wrapper Skeletons"]
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"]
Module 04 becomes much easier to trust once decorators stop looking like syntax magic and start looking like ordinary higher-order functions.
The first sentence to make ordinary is:
a decorator is fundamentally a callable that takes a function and returns another callable.
That returned callable is usually built with a nested function that closes over the original function and any wrapper state.
The sentence to keep¶
When you meet a decorator, ask:
what is the original function, what nested wrapper was built around it, and what state does that wrapper close over?
That question keeps the mechanics visible before policy or style enters.
The wrapper skeleton¶
At the most basic level, a decorator looks like this:
This is not yet useful, but it shows the whole structure:
decoratorreceives the original functionwrapperis nested inside itwrapperdelegates to the original functiondecoratorreturns the wrapper
That is the entire mechanical basis of function-wrapping decorators.
Closures make the wrapper remember the original function¶
The nested wrapper keeps access to func through closure semantics:
funclives in the outer scopewrappercloses over it- later calls to
wrapperstill reach the original function through that closure
That is why no global variable is required. The wrapper carries the original callable with it as runtime state.
One picture of the structure¶
graph TD
decorator["decorator(func)"]
func["original function"]
wrapper["nested wrapper(*args, **kwargs)"]
closure["closure remembers func and wrapper state"]
decorator --> func
decorator --> wrapper --> closure
Caption: the wrapper is not magic replacement code; it is an ordinary function carrying a closed-over reference to the original callable.
A simple example¶
def simple_decorator(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 = simple_decorator(greet)
print(greet_wrapped("Alice"))
That example already teaches several important things:
- the wrapper can do work before delegation
- the original return value can still flow through
- the wrapped callable is just another function object
State can live in the closure too¶
Wrappers do not only remember the original function. They can also remember wrapper-local state.
def counter_decorator(func):
count = 0
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"Call {count} to {func.__name__}")
return func(*args, **kwargs)
return wrapper
This is one of the first moments where wrapper design starts changing semantics:
- the wrapper is no longer only forwarding
- the wrapper now owns state across calls
That is why later pages in this module will distinguish thin wrappers from stateful policy-carrying wrappers.
Returning the wrapper is not optional¶
One of the simplest decorator bugs is forgetting to return the nested function:
def broken_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
# forgot: return wrapper
If that function is used as a decorator, the original function name gets rebound to
None, and the next call fails immediately.
This is a good reminder that decoration is ordinary rebinding. If the decorator returns the wrong thing, the name now points at the wrong thing.
Delegation should target the closed-over original function¶
Another common bug is accidentally calling a global name that may already have been rebound instead of calling the closed-over original function.
The safe mental model is:
- the wrapper should delegate to the
funcit closed over - not to a global name that could now refer to the wrapper itself or another layer
That rule prevents recursion mistakes and keeps the wrapper path explicit.
*args, **kwargs are a forwarding convenience, not a full story¶
Most simple wrappers start with:
That is fine as a forwarding skeleton, but it does not preserve the visible signature by itself. Later in the module and the next module, that distinction matters a lot.
For now, the important point is mechanical:
*args, **kwargslet the wrapper accept arbitrary calls- signature transparency is a separate concern
Let exceptions propagate unless the wrapper advertises a change¶
Thin wrappers should normally preserve the original error behavior:
- if the original function raises, the wrapper should usually let it raise
- catching or rewriting errors changes semantics and review cost
This is a useful early wrapper rule: if you change exception behavior, that is no longer "just logging" or "just timing." It becomes policy.
Review rules for wrapper skeletons¶
When reviewing a decorator's basic structure, keep these questions close:
- what original callable is being closed over?
- what wrapper-local state is being captured?
- does the wrapper delegate to the closed-over function or to a rebinding-prone name?
- does the decorator actually return a callable wrapper?
- is the wrapper preserving exception behavior unless it explicitly documents a change?
What to practice from this page¶
Try these before moving on:
- Write one bare logging decorator without
@syntax yet. - Add closure-held state with a counter and explain why
nonlocalis required. - Break a decorator by forgetting
return wrapper, then explain what name rebinding went wrong.
If those feel ordinary, the next step is to make the rebinding explicit through
@decorator syntax and stacked decoration.
Continue through Module 04¶
- Previous: Overview
- Next: Decorator Syntax and Definition-Time Rebinding
- Practice: Exercises
- Terms: Glossary