Decorators in Python
Decorators in Python are a powerful tool for modifying the behavior of functions or methods without altering their actual code. They are widely used in logging, access control, memoization, and more.
1. What is a Decorator?
A decorator is a function that takes another function as an argument and extends or modifies its behavior without changing its source code.
def my_decorator(func):
def wrapper():
print("Something before the function runs")
func()
print("Something after the function runs")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
2. Using Arguments in a Decorator
def repeat_twice(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper
@repeat_twice
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
3. Returning Values from a Decorated Function
def double_return(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
@double_return
def number():
return 10
print(number()) # Output: 20
4. Using functools.wraps to Preserve Function Metadata
from functools import wraps
def log_function(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_function
def add(a, b):
"""Adds two numbers."""
return a + b
print(add.__name__) # Output: add
print(add.__doc__) # Output: Adds two numbers.
5. Applying Multiple Decorators
def uppercase(func):
def wrapper():
return func().upper()
return wrapper
def exclamation(func):
def wrapper():
return func() + "!!!"
return wrapper
@uppercase
@exclamation
def greet():
return "hello"
print(greet()) # Output: HELLO!!!
6. Class-Based Decorators
class RepeatThreeTimes:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
for _ in range(3):
self.func(*args, **kwargs)
@RepeatThreeTimes
def say_hi():
print("Hi!")
say_hi()
7. Real-World Use Cases
7.1 Timing Function Execution
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
time.sleep(2)
print("Function finished!")
slow_function()
7.2 Caching with lru_cache
from functools import lru_cache
@lru_cache(maxsize=10)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(30)) # Much faster due to caching