Python > Advanced Python Concepts > Decorators > Function Decorators
Simple Function Decorator
This example demonstrates a basic function decorator in Python. Function decorators are a powerful and elegant way to modify or enhance the behavior of functions without changing their actual code. This snippet shows how to define a decorator that wraps a function, adding functionality before and after the function's execution.
Basic Decorator Structure
This code defines a decorator `my_decorator` that takes a function `func` as input. Inside `my_decorator`, a wrapper function `wrapper` is defined. The `wrapper` function adds some functionality (printing before and after messages) and then calls the original function `func`. The `@my_decorator` syntax is syntactic sugar for `say_hello = my_decorator(say_hello)`.
def my_decorator(func):
def wrapper():
print('Before function execution')
func()
print('After function execution')
return wrapper
@my_decorator
def say_hello():
print('Hello!')
say_hello()
Concepts Behind the Snippet
Decorators are built upon the concept of first-class functions in Python. This means that functions can be treated as variables, passed as arguments to other functions, and returned as values from functions. The decorator itself is a function that takes another function as an argument, adds some functionality, and returns a new function. The `@` symbol is just a cleaner way to apply the decorator.
Real-Life Use Case Section
Decorators are commonly used for logging, authentication, access control, timing function execution, and caching results. For example, you could use a decorator to log every time a function is called or to check if a user has the necessary permissions to execute a function. They centralize common logic, improving code reusability and readability.
Best Practices
When creating decorators, it's important to use the `functools.wraps` decorator. This ensures that the original function's metadata (name, docstring, etc.) is preserved. Otherwise, debugging can become more difficult, as the decorated function's metadata will be replaced with the wrapper's metadata.
Interview Tip
Be prepared to explain the syntax `@decorator_name` and how it translates to `func = decorator_name(func)`. Also, understand the concept of closures, as decorators heavily rely on them.
When to use them
Use decorators when you have repetitive code that you want to apply to multiple functions or methods. Decorators promote code reuse and make your code more readable and maintainable.
Memory footprint
Decorators generally have a minimal impact on memory footprint. The primary memory overhead comes from the creation of the wrapper function and the storage of the original function's code. This impact is typically negligible unless you are dealing with a very large number of decorated functions.
Alternatives
Alternatives to decorators include manually wrapping functions or using inheritance. However, decorators offer a more elegant and concise solution, especially when you need to apply the same logic to multiple functions.
Pros
Pros of using decorators include improved code readability, increased code reusability, and separation of concerns. They allow you to keep your function logic clean and focused while adding extra functionality in a modular way.
Cons
Cons of using decorators can include increased complexity if not used properly, potential for confusion when debugging if metadata is not preserved, and a slight performance overhead due to the extra function call. It's important to use decorators judiciously and document their behavior clearly.
FAQ
-
What does the `@` symbol do?
The `@` symbol is syntactic sugar for applying a decorator to a function. It's equivalent to writing `func = decorator(func)`. -
How do I pass arguments to the decorated function?
The wrapper function in the decorator needs to accept arbitrary arguments (`*args`, `**kwargs`) and pass them to the original function.