Python Decorator
A decorator in Python is the design pattern that enables one to modify or extend the behavior of functions or methods without permanently changing them. This is a powerful, flexible feature of Python, extensively applied for logging, access control enforcement, instrumentation, memoization, and much more.
Key Concepts
- First-Class Functions:
- Functions are first-class objects in Python; that means they can be passed as arguments to other functions, returned from functions, and assigned to variables.
2. Higher-Order Functions:
- A function that takes another function as an argument or returns a function is called a higher-order function.
3. Closures:
- A closure is a function object that has access to variables in its scope even after the outer function has finished executing.
What is a Decorator?
A decorator is essentially a higher-order function that takes a function as input and returns a new function with added functionality.
Syntax
The @decorator_name
syntax is a shorthand for applying a decorator to a function. For example:
@decorator_name
def my_function():
pass
is equivalent to:
def my_function():
pass
my_function = decorator_name(my_function)
Basic Example
def simple_decorator(func):
def wrapper():
print("Before the function call")
func()
print("After the function call")
return wrapper
@simple_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Before the function call
Hello!
After the function call
simple_decorator
: Takessay_hello
as an argument and wraps its behavior inwrapper
.wrapper
: Adds additional behavior before and after calling the original function.
Decorators with Arguments
To create a decorator that accepts arguments, you need a function that returns a decorator.
def decorator_with_args(arg):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"Decorator argument: {arg}")
return func(*args, **kwargs)
return wrapper
return decorator
@decorator_with_args("Hello")
def greet(name):
print(f"Greetings, {name}!")
greet("Alice")
Output:
Decorator argument: Hello
Greetings, Alice!
Common Use Cases
1. Logging
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with arguments {args} and {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
add(2, 3)
Output:
Calling add with arguments (2, 3) and {}
add returned 5
2. Authentication
def authenticate_decorator(func):
def wrapper(user, *args, **kwargs):
if user.get("is_authenticated"):
return func(user, *args, **kwargs)
else:
print("Authentication failed!")
return wrapper
@authenticate_decorator
def show_dashboard(user):
print(f"Welcome, {user['name']}!")
user = {"name": "Alice", "is_authenticated": True}
show_dashboard(user)
Output:
Welcome, Alice!
3. Memoization (Caching)
def memoize(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
print(factorial(5)) # Calculated
print(factorial(5)) # Cached
Using functools.wraps
To preserve the metadata of the original function (like its name, docstring, etc.), use functools.wraps
.
from functools import wraps
def simple_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Before the function call")
result = func(*args, **kwargs)
print("After the function call")
return result
return wrapper
@simple_decorator
def say_hello():
"""This is a greeting function."""
print("Hello!")
print(say_hello.__name__) # Outputs: say_hello
print(say_hello.__doc__) # Outputs: This is a greeting function.
Key Points to Remember
- Decorators are one of the ways to extend the functionality of functions.
- The
@decorator
syntax is a short form of passing a function to a decorator. - Use
*args
and**kwargs
to handle functions with varying arguments. - Use
functools.wraps
to retain the original function’s metadata. - Decorators can be stacked. Many decorators can be applied to one function.