Python Decorators
Common interview questions on this topic — practice explaining concepts out loud.
Here is Interview Prep Q&A module focused on Python Decorators, designed to help you prepare for intermediate-level technical interviews, while this module draws upon core concepts, practical patterns and advanced architectures discussed in the Python Decorator tutorial and quiz materials.
Interview Prep Q&A: Python Decorators
Question: How do Python decorators work under the hood, and what core programming concepts make them possible?
Answer: At their core, decorators are wrapper functions that temporarily modify or extend a behavior with another function without permanently altering its original code. Python utilizes the @ syntax as "syntactic sugar" to apply them easily. Under the hood, decorators rely on two foundational concepts:
1. Functions as first-class objects: In Python, functions are treated just like regular variables. They can be saved to variables, placed inside lists, and passed as arguments into higher-order functions.
2. Closures: This concept allows the child function (an inner wrapper) to remember and access variables from its parent function's enclosing scope, even after the parent function has finished executing. An inner wrapper acts like an invisible backpack carrying the original function so it can be executed safely inside the new logic.
Question: When writing a custom decorator, why is it considered a professional standard to use functools.wraps? What happens if you omit it?
Answer: If you omit functools.wraps, the original function gets completely replaced by the decorator's inner wrapper function. As the result the original function loses its identity and metadata, while for instance, calling print(my_function.__name__) will return "wrapper" instead of the original function's actual name and its docstrings will just be lost, and this creates massive headaches when reading logs or debugging complex code.
Using @wraps(func) from functools library (applied directly to the inner wrapper function) safely preserves and copies the original function's name, identity, and documentation over to a new wrapper.
Question: Imagine you're actually a backend developer who needs to measure the execution time of multiple functions across your application, and how would really you design a @speed_tracker decorator that can successfully wrap functions using varying, unknown numbers of arguments?
Answer: Towards ensure a decorator can handle any function regardless of its signature, the inner wrapper function must accept *args and **kwargs. This essentially tells the wrapper for gather all positional and keyword arguments passed by user and hand them perfectly to the original function.
Here is how you would implement it:
import time
from functools import wraps
def speed_tracker(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
# Execute the original function with its arguments
result = func(*args, **kwargs)
end_time = time.time()
print(f"Executed in {end_time - start_time} seconds")
return result
return wrapper
@speed_tracker
def complex_math(a, b, multiplier=1):
return (a + b) * multiplier
Question: What happens when you stack multiple decorators on a single function; explain execution order and potential pitfalls of doing so. Answer: Python allows you to stack multiple decorators on top of a single function but the order into which they execute is critical. The decorators are actually applied from "bottom up," meaning the decorator closest towards the function definition runs first;
a common pitfall is stacking them in the wrong order which completely alters the logic. Towards example, if you stack a @speed_tracker above an @rate_limit decorator, your performance timer will accidentally measure the execution speed of the rate limiter logic itself, rather than the core function you intended towards track. Also, heavily stacked decorators introduce performance overhead as Python has to jump through extra hoops into memory every time function is called.
Question: Sometimes you need to pass instructions directly to the decorator itself, such as creating an API limiter defined as @rate_limit(max_requests=5). How does the architecture of a decorator change to accept its own arguments?
Answer: To build a parameterized decorator ( decorator that accepts arguments), you must add an extra layer of nesting, resulting in three total layers of nested functions; leveraging Python's LEGB (Local, Enclosing Global, Built-in) scope rules, the outer layers act as closures that pass down data to the core wrapper.
- ** Outermost Layer**: Captures the decorator's argument (e.g.,
max_requests = 5). - The Middle Layer: Captures the original function being decorated.
- An Innermost Layer: The actual wrapper that executes the logic (like tracking request counts) and eventually calls the original function.
def rate_limit(max_requests):
# Outermost layer captures the setting
def decorator(func):
# Middle layer captures the function
@wraps(func)
def wrapper(*args, **kwargs):
# Innermost layer runs the wrapper logic
print(f"Checking if user is under {max_requests} requests...")
return func(*args, **kwargs)
return wrapper
return decorator
Learn Together
Share a learning session in real-time with a classmate.
Share this 6-digit key with your classmate to start learning together:
Room Details
Share this 6-digit room key with others so they can join you in real-time:
Instructions: Open any course page, click "Learn Together", and click "Join Room" to enter the code.