Skip to content

Python 装饰器完整指南

Python 装饰器是 Python 中一个非常强大的功能,它是通过函数来增强或修改其他函数的行为。简而言之,装饰器是一个包装器,可以用来"装饰"一个函数,使其在不修改源代码的情况下,增加额外的功能。装饰器在很多 Python 框架中都有广泛应用,比如 Django、Flask 等。

1. 装饰器的概念

装饰器本质上是一个函数,它接受另一个函数作为参数,并返回一个新的函数。通过这种方式,装饰器能够在函数执行前后加入额外的功能,而不改变原函数的代码。

1.1. 装饰器的基本语法

1.1.1. 装饰器的函数结构

python
def decorator(func):
    def wrapper():
        print("Before function call")
        func()  # 调用原函数
        print("After function call")
    return wrapper
  • decorator 是装饰器函数,接收一个函数 func 作为参数。
  • wrapper 是一个内部函数,它在执行原函数前后加入额外的行为。

1.1.2. 使用装饰器

python
@decorator  # 装饰器语法
def hello():
    print("Hello, world!")

hello()

这段代码等同于:

python
def hello():
    print("Hello, world!")

hello = decorator(hello)
hello()

1.1.3. 装饰器的执行流程

  1. 当你使用 @decorator 语法时,Python 会自动调用 decorator(hello),并将 hello 作为参数传递给 decorator
  2. decorator 返回一个新的函数 wrapper,然后将原函数 hello 替换为 wrapper
  3. 当你调用 hello() 时,实际上调用的是 wrapper(),而 wrapper 会先执行一些附加的操作,再调用原函数 hello()

1.2. 保留原函数元信息 - functools.wraps

装饰器会改变函数的元信息(如函数名、文档字符串等),使用 @functools.wraps 可以保留这些信息。

python
import functools

# 不使用 functools.wraps 的问题
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def example_func():
    """This is an example function."""
    pass

print(example_func.__name__)  # wrapper (不是原函数名)
print(example_func.__doc__)   # None (丢失了文档字符串)

# 使用 functools.wraps 的正确做法
def good_decorator(func):
    @functools.wraps(func)  # 保留原函数的元信息
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def example_func2():
    """This is an example function."""
    pass

print(example_func2.__name__)  # example_func2
print(example_func2.__doc__)   # This is an example function.

1.3. 多个装饰器的执行顺序

当一个函数被多个装饰器装饰时,执行顺序是从内到外(从下到上)。

  • 设置时的装饰顺序(从下到上)
  • 执行时的调用顺序(从上到下)
python
def decorator_one(func):
    print("Decorator One - Setup")
    def wrapper(*args, **kwargs):
        print("Decorator One - Before")
        result = func(*args, **kwargs)
        print("Decorator One - After")
        return result
    return wrapper

def decorator_two(func):
    print("Decorator Two - Setup")
    def wrapper(*args, **kwargs):
        print("Decorator Two - Before")
        result = func(*args, **kwargs)
        print("Decorator Two - After")
        return result
    return wrapper

@decorator_one
@decorator_two
def hello():
    print("Hello World")

# 设置时的装饰顺序(从下到上):
# 先执行:hello = decorator_two(hello)
# 后执行:hello = decorator_one(decorator_two(hello))
# 因此输出:
# Decorator Two - Setup
# Decorator One - Setup

hello()
# 执行时的调用顺序(从上到下):
# Decorator One - Before
# Decorator Two - Before
# Hello World
# Decorator Two - After
# Decorator One - After

2. 常见应用场景

2.1. 日志记录(也可以用来调试输出一些值)

装饰器可以用来记录函数执行的日志。

python
import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with arguments {args} and keyword arguments {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log
def add(a, b):
    return a + b

add(3, 5)

输出:

plain
Calling add with arguments (3, 5) and keyword arguments {}
8

2.2. 权限验证

你可以使用装饰器来验证用户是否有权限访问某些功能。

python
import functools


def has_permission():   # 实际需要替换验证逻辑
    return True


def require_permission(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if not has_permission():  # 权限检查函数
            print("Permission denied")
            return
        return func(*args, **kwargs)
    return wrapper


@require_permission
def delete_item(item):
    print(f"Deleting {item}")


delete_item("file.txt")

2.3. 性能计时

装饰器可用于函数执行时间的统计和分析,通过记录函数调用前后的时间戳来计算执行耗时。

python
import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time:.4f} seconds to execute")
        return result
    return wrapper

@timer
def long_running_task():
    time.sleep(2)

long_running_task()

输出:

plain
long_running_task took 2.0001 seconds to execute

3. 装饰器的进阶概念

3.1. 带参数的装饰器

  • 自定义mcp框架的时候使用这个可以方便的实现注册工具函数!
  • 需要注意的是外层函数可以接受其它参数,但是func必须是有装饰器来接受,这里就是decorator

有时你需要给装饰器传递参数。你可以通过嵌套函数来实现这一点。

python
import functools

def repeat(n):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    print("Hello!")

say_hello()

输出:

plain
Hello!
Hello!
Hello!

3.2. 装饰器的返回值

装饰器可以返回任何类型的数据,而不仅仅是原函数的返回值。你可以让装饰器改变函数的返回值。

  • 在这里是装饰器内部直接调用了我们的func然后对返回值修改
python
import functools

def multiply_by_two(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result * 2
    return wrapper

@multiply_by_two
def get_number():
    return 5

print(get_number())  # 10

3.3. 类装饰器

  • 这里的func参数是直接在类的__init__中接受的参数!

装饰器不仅可以是函数,也可以是类。类装饰器通过实现 __call__ 方法来工作。

python
import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} has been called {self.count} times")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

say_hello()  # say_hello has been called 1 times
say_hello()  # say_hello has been called 2 times

3.4. 装饰类的装饰器

装饰器不仅可以装饰函数,还可以装饰类。这里使用类方法也可以实现类似的效果!

python
def add_method(cls):
    def new_method(self):
        print(f"This is a dynamically added method to {cls.__name__}")
    cls.dynamic_method = new_method
    return cls

@add_method
class MyClass:
    def __init__(self, name):
        self.name = name

obj = MyClass("test")
obj.dynamic_method()  # This is a dynamically added method to MyClass

3.5. 装饰实例方法时的注意事项

  • 装饰类的实例方法的时候需要self参数
python
import functools

def method_decorator(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):  # 注意要包含 self 参数
        print(f"Decorating method {func.__name__} of {self.__class__.__name__}")
        return func(self, *args, **kwargs)
    return wrapper

class MyClass:
    @method_decorator
    def my_method(self):
        print("Original method")

obj = MyClass()
obj.my_method()

输出:

plain
Decorating method my_method of MyClass
Original method

4. 装饰器的实际使用

  1. Flask 路由装饰器@app.route('/path') 用于将 URL 映射到视图函数。
  2. Django 视图装饰器@login_required 装饰器用于在视图函数前验证用户是否已登录。
  3. 缓存装饰器:用来缓存函数的返回值,避免重复计算。
  4. 重试装饰器:在函数执行失败时自动重试。
  5. 异步装饰器:用于处理异步函数的装饰。

邬东升的博客