Usually shrouded in mystery at first glance, Python decorators are, at their core, functions wrapped around other functions to provide extra functionality without altering the key logic in the function being “decorated”. They’re a powerful tool for code reuse and readability, but can seem complex initially. This guide breaks down seven practical decorator tricks to elevate your Python skills.
Understanding the Basics
Before diving into tricks, let’s recap. A decorator is a function that takes another function as an argument and returns a modified version of it. The `@` symbol provides syntactic sugar for applying decorators.
def my_decorator(func):
def wrapper():
print("Before the function call")
func()
print("After the function call")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello() # Output: Before the function call, Hello!, After the function callTrick 1: Passing Arguments to Decorated Functions
Decorating functions that accept arguments requires a bit more finesse. The inner `wrapper` function needs to handle those arguments and pass them on.
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before the function call")
result = func(*args, **kwargs)
print("After the function call")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(5, 3)) # Output: Before the function call, 8, After the function callTrick 2: Preserving Function Metadata
When you decorate a function, its original metadata (like `__name__` and docstrings) can be lost. Use functools.wraps to preserve this information.
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Before the function call")
result = func(*args, **kwargs)
print("After the function call")
return result
return wrapper
def say_hello():
"'This is a simple hello function.'"
print("Hello!")
say_hello.__name__ # Output: say_hello (instead of wrapper)Trick 3: Decorating Class Methods
Decorating class methods requires special handling to ensure the `self` argument is correctly passed.
class MyClass:
def my_method(self):
print("Original method")
@classmethod
def decorator_my_method(cls):
def wrapper(self):
print("Before the method call")
cls.my_method(self)
print("After the method call")
return wrapper
MyClass.my_method = MyClass.decorator_my_method()
instance = MyClass()
instance.my_method() # Output: Before the method call, Original method, After the method callTrick 4: Chaining Decorators
You can apply multiple decorators to a single function. Decorators are applied from top to bottom.
def decorator1(func):
def wrapper():
print("Decorator 1")
func()
print("Decorator 1 - End")
return wrapper
def decorator2(func):
def wrapper():
print("Decorator 2")
func()
print("Decorator 2 - End")
return wrapper
@decorator2
@decorator1
def say_hello():
print("Hello!")
say_hello() # Output: Decorator 2, Hello!, Decorator 2 - End, Decorator 1, Decorator 1 - EndTrick 5: Using Decorators for Logging
Decorators are excellent for adding logging functionality without cluttering the core logic.
import functools
import logging
logging.basicConfig(level=logging.INFO)
def log_function_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} returned: {result}")
return result
return wrapper
@log_function_calls
def add(a, b):
return a + b
add(2, 3) # Logs function call and return valueTrick 6: Decorators with Arguments
Decorators themselves can accept arguments to customize their behavior.
def repeat(num_times):
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def say_hello():
print("Hello!")
say_hello() # Output: Hello!, Hello!, Hello!Trick 7: Decorators for Caching
Caching frequently used function results can significantly improve performance. Decorators provide a clean way to implement this.
def cache(func):
cache_dict = {}
@functools.wraps(func)
def wrapper(*args):
if args in cache_dict:
return cache_dict[args]
else:
result = func(*args)
cache_dict[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(5)) # Calculates and caches, subsequent calls return cached valueMastering these decorator tricks will not only make your code cleaner but also demonstrate a deeper understanding of Python's functional programming capabilities.
Source: Read the original article here.
Discover more tech insights on ByteTrending.
Discover more from ByteTrending
Subscribe to get the latest posts sent to your email.











