Loading Page Content...
Preparing courses, live labs, and learning resources.सामग्री लोड हो रही है, कृपया प्रतीक्षा करें...
Preparing courses, live labs, and learning resources.सामग्री लोड हो रही है, कृपया प्रतीक्षा करें...
Decorators represent one of Python's most elegant and powerful design patterns. At their architectural core, decorators are an application of Higher-Order Functions and Closures, allowing developers to dynamically inject cross-cutting concerns—such as audit logging, execution telemetry, authorization checks, caching, and rate limiting—without altering the underlying function's source code.
To master decorators, one must understand Python's execution model regarding functions:
When an inner function closes over free variables, CPython stores them inside the wrapper's __closure__ attribute as an array of cell objects:
The @ decorator syntax introduced in PEP 318 is pure syntactic sugar for variable re-binding:
functools.wrapsWhen a function is wrapped, the outer variable name points to wrapper. Without intervention, critical metadata—such as __name__, __doc__, __annotations__, and __module__—is overwritten by the wrapper's metadata. This breaks automated documentation generators, IDE tooltips, and reflection tools.
The standard library provides @functools.wraps to mirror attributes and attach the original function via __wrapped__:
To pass configuration arguments to a decorator (e.g. @retry(max_attempts=3, delay=1.0)), you need a Decorator Factory—a function that accepts configuration arguments and returns the actual decorator.
When multiple decorators are stacked upon a single function, they are applied from bottom to top (inside out), but the resulting wrappers execute from top to bottom (outside in):
| Pattern | Structure | Usage |
|---|---|---|
| Simple Decorator | 2-level function nesting (decorator -> wrapper) | When no external arguments are needed. |
| Parameterized Decorator | 3-level function nesting (factory -> decorator -> wrapper) | When decorator parameters or configurations are needed. |
| Metadata Protection | @functools.wraps(func) on wrapper | Preserves signature, __name__, and __doc__. |
| Unwrapping Target | wrapper.__wrapped__ | Allows inspection or testing of the raw decorated function. |
@functools.wraps(func)Without @functools.wraps, the decorated function loses its original __name__, __doc__, and type annotations, breaking automated doc generators, debuggers, and inspection tools.
A decorator that accepts arguments requires three levels of nested functions: the outer factory function, the decorator function, and the wrapper function.
Build a customizable decorator factory @retry(max_retries=3, delay=1.0) that retries a failing function before re-raising the exception.
What does the expression @my_decorator placed directly above def my_func(): pass actually do behind the scenes? A. Compiles my_func to native machine code. B. Re-binds the function identifier via my_func = my_decorator(my_func). C. Executes my_func immediately in a separate thread. D. Creates a metaclass named my_decorator.
@ decorator syntax is syntactic sugar that passes the defined function into the decorator callable and re-assigns the returned callable to the original function identifier.Why is it considered a strict best practice to use @functools.wraps(func) inside custom decorator wrappers? A. It speeds up function execution by 50%. B. It automatically handles thread synchronization locks. C. It preserves the original function's metadata such as __name__, __doc__, and __annotations__. D. It prevents any exceptions from propagating.
@functools.wraps copies attributes such as __name__, __doc__, and __annotations__ from the decorated target to the wrapper function, and exposes __wrapped__ for introspection.How many nested function scopes are required to implement a decorator that accepts configuration parameters (e.g., @rate_limit(requests_per_sec=5))? A. 1 B. 2 C. 3 D. 4
Given two stacked decorators @auth_required and @cache_response placed above def fetch_data(): ..., in what order are they applied during definition? A. @auth_required first, then @cache_response B. @cache_response first (bottom), then @auth_required (top) C. They are applied in parallel using asynchronous tasks D. The order is randomly determined at runtime
fetch_data = auth_required(cache_response(fetch_data)). Therefore, cache_response is applied first.How can a developer inspect or invoke the original undecorated function when a decorator used @functools.wraps? A. func.__raw__() B. func.__wrapped__ C. func.__original__ D. It is permanently discarded and cannot be accessed.
@functools.wraps attaches the original undecorated callable to the __wrapped__ attribute on the wrapper function.In the next lesson, we will continue your advanced Python journey with Class Decorators (3: Advanced Decorators and Context Managers).
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
| Previous Lesson | Next Lesson |
|---|---|
| Project: Vector Class with Overloaded Operators | Class Decorators |
Test your understanding of this lesson with 5 questions. Each question has one correct answer.