Master Python Decorators: A Complete Guide from Basics to Advanced
This article explains what Python decorators are, why they are useful for cross‑cutting concerns, and walks through basic, parameterized, class‑based, stacked, and advanced AOP examples, while highlighting common pitfalls, best‑practice tips, and standard library utilities.
1. What is a decorator?
A decorator is essentially a function that takes another function as an argument and returns a new function, adding extra behavior without modifying the original code.
def my_decorator(func):
def wrapper():
print("调用函数前做点什么")
func()
print("调用函数后做点什么")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# 输出:
# 调用函数前做点什么
# Hello!
# 调用函数后做点什么2. Why use decorators?
In real projects the same logic—logging, timing, permission checks, caching, transaction management, retry mechanisms—often needs to be added to many functions. Writing that code repeatedly makes functions ugly and hard to maintain; decorators separate these cross‑cutting concerns from core business logic.
Logging
Timing
Permission validation
Caching results
Transaction management
Retry mechanisms
3. Basic decorator (no parameters)
import functools
def logger(func):
@functools.wraps(func) # preserve original metadata
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__},参数: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} 返回: {result}")
return result
return wrapper
@logger
def add(a, b):
return a + b
add(3, 5)
# 输出:
# 调用 add,参数: (3, 5), {}
# add 返回: 8Key point: @functools.wraps(func) is essential; otherwise the wrapper overwrites __name__, __doc__, etc.
4. Parameterized decorator
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
raise
print(f"尝试 {attempt} 失败,重试中...")
return None
return wrapper
return decorator
@retry(max_attempts=5)
def unstable_api():
import random
if random.random() < 0.7:
raise ValueError("随机失败")
return "成功"Structure analysis: retry receives parameters and returns the real decorator decorator. decorator receives the target function and returns wrapper. wrapper is the function that gets executed.
5. Class decorator
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func) # preserve metadata
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hi():
print("Hi!")
say_hi()
say_hi()
# 输出:
# say_hi 已被调用 1 次
# Hi!
# say_hi 已被调用 2 次Class decorators can maintain state, making the logic clearer.
6. Stacking decorators
@logger
@timer
def compute():
pass
# Equivalent to compute = logger(timer(compute))Execution order: the wrapper of timer runs first, then the wrapper of logger; return flow is reversed.
7. Common use‑case library
7.1 Timing decorator
import time, functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 耗时 {end-start:.4f} 秒")
return result
return wrapper7.2 Memoization (cache)
def cache(func):
memo = {}
@functools.wraps(func)
def wrapper(*args):
if args in memo:
return memo[args]
result = func(*args)
memo[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)Note: mutable arguments must be converted to immutable (e.g., tuple(args)). Python 3.9+ can use functools.lru_cache.
7.3 Permission check (web frameworks)
def require_login(func):
@functools.wraps(func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
raise PermissionError("请先登录")
return func(request, *args, **kwargs)
return wrapper
@require_login
def dashboard(request):
return "欢迎回来"7.4 Retry with exponential backoff
def retry_with_backoff(max_retries=3, base_delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for i in range(max_retries):
try:
return func(*args, **kwargs)
except Exception:
if i == max_retries - 1:
raise
delay = base_delay * (2 ** i) # exponential backoff
time.sleep(delay)
return None
return wrapper
return decorator7.5 Singleton pattern
def singleton(cls):
instances = {}
@functools.wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self):
print("创建数据库连接")7.6 Simple type‑checking decorator
def type_check(**types):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for arg_name, expected_type in types.items():
value = kwargs.get(arg_name)
if value is not None and not isinstance(value, expected_type):
raise TypeError(f"{arg_name} 应为 {expected_type},实际是 {type(value)}")
return func(*args, **kwargs)
return wrapper
return decorator
@type_check(name=str, age=int)
def greet(name, age):
print(f"{name} 今年 {age} 岁")8. Advanced: Decorators for Aspect‑Oriented Programming (AOP)
Decorators are the primary way to implement AOP in Python, separating logging, transaction, security, etc., from business logic.
def transactional(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
session = get_db_session()
try:
result = func(*args, **kwargs)
session.commit()
return result
except Exception:
session.rollback()
raise
finally:
session.close()
return wrapper
@transactional
def transfer_money(from_id, to_id, amount):
# 业务逻辑:扣钱、加钱
pass9. Common pitfalls and best practices
Forget functools.wraps → always add @functools.wraps(func).
Modifying mutable arguments inside a decorator can cause side effects → avoid mutation or deep‑copy.
Class‑decorator instances share state across functions → create a separate instance per function, e.g., pass the function to __init__.
Incorrect decorator order → understand that execution proceeds from the bottom (closest to the function) upward.
When decorating class methods, remember to forward self correctly in the wrapper.
def method_logger(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
print(f"调用 {self.__class__.__name__}.{func.__name__}")
return func(self, *args, **kwargs)
return wrapper10. Standard‑library decorators
@staticmethod– defines a static method. @classmethod – defines a class method (first argument cls). @property – turns a method into a read‑only attribute. @abstractmethod – marks a method as abstract (used with ABC). @functools.lru_cache – caches function results. @contextlib.contextmanager – defines a context manager with a generator. @dataclass – automatically generates __init__ and other methods.
Conclusion
Decorators are a powerful, elegant feature of Python that enable concise code and underpin many popular frameworks such as Flask, Django, and Click. Mastering them lets you write cleaner code and understand the core mechanisms of these frameworks.
Suggested hands‑on exercises:
Implement @delay(seconds=5) to postpone function execution.
Implement @limit_calls(max_calls=3) to restrict call count and raise an exception after the limit.
Read the implementation of @app.route in Flask’s source code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
