Python > Advanced Topics and Specializations > Meta-programming > Decorators
Simple Decorator Example: Timing Function Execution
This example demonstrates a basic decorator that measures and prints the execution time of a function. Decorators are a powerful feature in Python that allows you to modify the behavior of functions or methods in a clean and readable way.
Concepts Behind Decorators
Decorators are essentially syntactic sugar for wrapping a function with another function. They use the @
symbol followed by the decorator name to apply the wrapper. The decorator function takes the original function as an argument, extends its behavior (e.g., by adding logging or timing), and returns the modified function. This allows you to separate concerns and avoid code duplication.
The Timing Decorator
The timer
function is our decorator. It takes a function func
as input. Inside timer
, we define a wrapper
function that will be executed when the decorated function is called. The wrapper
measures the execution time before and after calling the original function func
. It then prints the execution time and returns the result of the original function. Finally, the timer
function returns the wrapper
function. The @timer
syntax above my_function
is equivalent to writing my_function = timer(my_function)
.
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f'Function {func.__name__!r} executed in {execution_time:.4f} seconds')
return result
return wrapper
@timer
def my_function(n):
time.sleep(n)
my_function(2)
Real-Life Use Case
Timing function execution is useful for identifying performance bottlenecks in your code. You can use this decorator to measure how long different parts of your application take to run, allowing you to focus your optimization efforts on the areas that will have the biggest impact. Other common use cases include logging, authentication, input validation, and caching.
Best Practices
functools.wraps
to preserve the original function's metadata (name, docstring, etc.).
Interview Tip
Be prepared to explain how decorators work under the hood. Understand the difference between decorating a function with @my_decorator
and manually applying the decorator using my_function = my_decorator(my_function)
. Also, be able to write a simple decorator from scratch.
When to Use Them
Use decorators when you need to add common functionality to multiple functions or methods without modifying their original code. They promote code reuse and improve readability by separating concerns. Decorators are well-suited for tasks like logging, timing, authorization, and validation.
Memory Footprint
Decorators generally have a minimal impact on memory footprint. They introduce a small overhead due to the additional function call (the wrapper). However, the benefits of code reuse and improved readability often outweigh this minor cost. Avoid creating excessively complex decorators that perform resource-intensive operations.
Alternatives
Alternatives to decorators include using inheritance, mixins, or manual function wrapping. However, decorators are often the most elegant and concise solution for adding cross-cutting concerns to functions.
Pros
Cons
FAQ
-
What is the difference between a decorator and a decorator factory?
A decorator is a function that takes another function as an argument and returns a modified function. A decorator factory is a function that returns a decorator. Decorator factories allow you to parameterize decorators with arguments. -
How do I preserve the original function's metadata when using a decorator?
Use thefunctools.wraps
decorator from thefunctools
module. This decorator copies the original function's metadata (name, docstring, etc.) to the wrapper function.