A decorator is a function that takes another function as an argument and extends or alters its behavior without explicitly modifying its source code. In Python, @decorator is syntactic sugar for func = decorator(func).
A decorator is executed at import time (when the Python script is parsed and loaded into memory). The function it wraps is executed later, at runtime, when explicitly called.
A closure is a dynamically generated function that remembers the state of the environment in which it was created. Specifically, it retains bindings to free variables (variables defined in the outer enclosing function) even after the outer function has finished executing.
When you have a nested function (a closure) and you want to reassign a free variable defined in the outer function. If you only want to read the free variable, or mutate it in place (like appending to a list), you don't need nonlocal. You only need it for the = assignment operator, to prevent Python from creating a new local variable that shadows the outer one.
You need a "decorator factory"—a function that takes the arguments and returns the actual decorator. This requires three levels of nested functions:
- The factory (takes the arguments, returns the decorator)
- The decorator (takes the function, returns the wrapper)
- The wrapper (takes
*args, **kwargs, executes the logic and calls the function).
When a decorator replaces a function with a wrapper, the original function's identity (its __name__, __doc__ docstring, and parameter signature) is lost and replaced by the wrapper's identity. @wraps is a decorator applied to the inner wrapper function that copies all this metadata from the original function to the wrapper. It is vital for debugging, introspection, and frameworks that rely on signatures.
When CPython compiles a function and detects that an inner function references a variable from an outer function, it creates a Cell Object.
Instead of storing the variable's value on the fast local call stack, it stores a reference to this cell object. Both the outer function and the inner closure contain pointers to this same cell. The inner function's bytecode uses the LOAD_DEREF instruction to access the value inside the cell. You can inspect these cells at runtime using the function's __closure__ attribute.