Python > Advanced Python Concepts > Decorators > Understanding Decorators
Simple Decorator Example: Logging Function Calls
Decorators are a powerful and elegant feature in Python that allows you to modify or enhance the behavior of functions or methods. They provide a way to wrap additional functionality around existing functions without modifying their core code. This example demonstrates a basic decorator that logs the name of the function being called.
Basic Decorator Structure
This code defines a decorator called `my_decorator`. It takes a function (`func`) as input and returns a new function called `wrapper`. The `wrapper` function performs some additional operations (in this case, printing the function name) before and after calling the original function. The `@my_decorator` syntax is syntactic sugar that applies the decorator to the `say_hello` function, effectively making `say_hello = my_decorator(say_hello)`.
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f'Calling function: {func.__name__}')
result = func(*args, **kwargs)
return result
return wrapper
@my_decorator
def say_hello(name):
print(f'Hello, {name}!')
say_hello('World')
Concepts Behind the Snippet
The core idea behind decorators is to wrap a function with another function to add functionality. This relies on Python's ability to treat functions as first-class objects, meaning you can pass them as arguments to other functions and return them as values. The `wrapper` function is crucial because it executes code *before* and *after* the decorated function runs.
Real-Life Use Case: Logging
Decorators are frequently used for logging function calls, as demonstrated in the example. They can also be used for authentication, authorization, input validation, performance monitoring, and caching. Imagine a scenario where you want to log every API request to your server. A decorator would be perfect for that.
Best Practices
Interview Tip
Be prepared to explain how decorators work, including the role of the `wrapper` function and the syntactic sugar `@`. Understand how to apply them to both functions and methods.
When to Use Them
Use decorators when you need to add functionality to multiple functions or methods in a consistent way, avoiding code duplication. They are especially useful for implementing cross-cutting concerns that are not directly related to the core logic of the functions being decorated.
Memory Footprint
Decorators introduce a small memory overhead due to the additional function call stack and potentially the storage of any data used by the decorator. However, this overhead is usually negligible unless you are dealing with a very large number of decorated functions or extremely memory-constrained environments.
Alternatives
Alternatives to decorators include directly modifying the functions themselves (which is less maintainable), using mixins (for class-based approaches), or using aspect-oriented programming (AOP) frameworks (for more complex scenarios). However, decorators generally provide the cleanest and most Pythonic solution for many common use cases.
Pros
Cons
FAQ
-
What is the purpose of the `wrapper` function?
The `wrapper` function is the key to the decorator pattern. It's the function that gets executed when you call the decorated function. It allows you to execute code *before* and *after* the original function call, adding functionality without directly modifying the original function's code. -
What does `@my_decorator` do?
The `@my_decorator` syntax is a shorthand for applying the decorator to the function. It's equivalent to writing `say_hello = my_decorator(say_hello)`. It essentially replaces the original function with the decorated version.