Foundations — Decorators — function decorators, @, wraps
Before you can build a decorator you must own every small idea it silently rests on. The parent note uses words like callable, closure, first-class, rebind, *args, __name__, @. If any of those feel fuzzy, the whole topic collapses. So we build each one from zero, in the exact order they stack.
1 — A function is a thing, not just an action
Most beginners picture a function as a verb: "do this." But in Python a function is also a noun — an object you can hold in a variable, exactly like the number 5 or the string "hi".
def greet(): return "hi"
x = greet # NO parentheses: x now IS the function
print(x) # <function greet at 0x...> — an object!
print(x()) # 'hi' — parentheses = "run it now"The single most important habit: greet is the thing; greet() runs the thing. Look at the two boxes below.

- Left box:
greet— a labelled box you can carry around. - Right box:
greet()— you pressed the button, out pops the result"hi".
Related: First-class functions.
2 — "Callable": the thing you can put () after
Why introduce this word instead of just "function"? Because a decorator's real contract is "takes a callable, returns a callable" — slightly wider than "function", so the same idea works on classes and objects too.
Item
f = the callable itself; f() = its return value.3 — Rebinding a name
A variable name in Python is a label stuck on a box. Assignment peels the label off one box and sticks it on another.
f = greet # label 'f' points at the greet function
f = deco(f) # peel 'f' off greet, stick it on deco's result
Follow the arrow: after f = deco(f), the name f no longer points at the original — it points at whatever deco returned. Nothing about the original code changed; only the label moved.
4 — Higher-order functions: functions that eat/return functions
A decorator is a specific shape of higher-order function: eats one function, returns one function. See Higher-order functions.
def apply_twice(fn): # takes a function
return fn(fn(0)) # calls it
def double(x): return x + 2
apply_twice(double) # double(double(0)) = 45 — Nested functions and the closure (the memory trick)
You can def a function inside another function. The inner one can see the outer one's variables — and it keeps seeing them even after the outer function has finished. That kept-alive memory is a closure.
def make_adder(n): # n lives here
def adder(x): # inner sees n
return x + n # n is a FREE variable of adder
return adder # hand back the inner, n still packed
add5 = make_adder(5)
add5(10) # 15 — it still remembers n = 5!
The figure shows adder walking away from make_adder carrying a backpack that still holds n = 5. That backpack is the closure.
6 — *args and **kwargs: the universal argument catcher
A wrapper must work for any function — one that takes 0 arguments, 5 arguments, keyword arguments, anything. Python gives two catch-all symbols.
def show(*args, **kwargs):
print(args) # a tuple of positional args
print(kwargs) # a dict of keyword args
show(1, 2, name="x") # (1, 2) and {'name': 'x'}The same stars used when calling do the reverse — they unpack:
func(*args, **kwargs) # spread the tuple/dict back into arguments7 — The @ symbol: sugar, not magic
@deco written above def f(): ... is pure shorthand for "define f, then rebind f = deco(f)." That's section 3's label-move, triggered automatically. It saves typing and puts the intent right at the top of the function.
8 — __name__ and __doc__: a function's name tag
Every function carries built-in labels — its identity card.
def add(a, b):
"adds two numbers"
return a + b
add.__name__ # 'add'
add.__doc__ # 'adds two numbers'Prerequisite map
Equipment checklist
Do I know the difference between f and f()?
f is the function object; f() runs it and gives the return value.What does "first-class" mean for functions?
What is a callable?
() after — functions, methods, __call__ objects.What does f = deco(f) do to the name f?
deco's returned function.What does @deco above def f desugar to?
f = deco(f).What is a closure?
What is a free variable?
What does *args collect, and what does **kwargs collect?
*args → a tuple of positional args; **kwargs → a dict of keyword args.What do * and ** do at a call site?
Why does the wrapper use *args, **kwargs?
What is func.__name__?
Why is functools.wraps needed?
'wrapper'; wraps copies the original's __name__/__doc__ back.Connections
- 1.3.08 Decorators — function decorators, @, wraps (Hinglish)
- First-class functions
- Higher-order functions
- Closures and free variables
- args and kwargs unpacking
- functools — wraps, lru_cache, partial
- Class decorators and property