Decorators in Python
Wrap a function in extra behaviour without touching its code
@decorator line is just a clean shortcut for reassigning a function to its wrapped self. This lets you add logging, timing, caching or access checks to many functions without editing any of them.
- explain why functions are first-class objects,
- write a nested wrapper function that closes over another function,
- build a decorator and apply it with
@, - forward any arguments with
*args, **kwargs, - preserve the original name and docstring with
functools.wraps.
1. Functions are first-class objects
Before decorators make sense, remember one fact: in Python a function is an ordinary object, just like an int or a list. You can store it in a variable, put it in a list, pass it into another function as an argument, and even return it from a function.
def shout(text):
return text.upper()
yell = shout # store the function object (no parentheses!)
print(yell("hello")) # HELLO
def run_twice(func, value):
return func(value) + " " + func(value)
print(run_twice(shout, "hi")) # HI HI
Notice yell = shout has no parentheses: we copy the function itself, not its result. This ability to pass functions around is exactly what a decorator relies on.
2. Nested functions and closures
You can define a function inside another function. The inner function can read variables from the outer one, and it keeps a reference to them even after the outer function has finished. An inner function that remembers values from its enclosing scope is called a closure.
def make_multiplier(factor):
def multiply(n):
return n * factor # 'factor' comes from the outer scope
return multiply # return the inner function object
triple = make_multiplier(3)
print(triple(10)) # 30
print(triple(4)) # 12
make_multiplier returns the inner multiply function, which still remembers factor = 3. A decorator is built exactly this way: an outer function that defines and returns an inner wrapper.