Fundamentals 13 min read

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.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Master Python Decorators: A Complete Guide from Basics to Advanced

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 返回: 8

Key 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 wrapper

7.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 decorator

7.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):
    # 业务逻辑:扣钱、加钱
    pass

9. 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 wrapper

10. 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PythonBest Practicescode examplesAspect-Oriented Programmingdecoratorsfunctools
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.