装饰器

Python 装饰器(Decorator)教程

1. 装饰器基础概念

装饰器是 Python 中一种强大的语法特性,它允许在不修改原函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。

1.1 函数作为对象

在 Python 中,函数也是对象,可以赋值给变量:

def now():
    print('2024-6-1')

f = now  # 将函数赋值给变量
f()      # 通过变量调用函数

每个函数对象都有一个 __name__ 属性:

print(now.__name__)  # 输出: 'now'
print(f.__name__)    # 输出: 'now'

2. 简单装饰器实现

2.1 基本装饰器

下面是一个简单的装饰器示例,它在函数调用前后打印日志:

def log(func):
    def wrapper(*args, **kwargs):
        print(f'调用 {func.__name__}() 函数')
        return func(*args, **kwargs)
    return wrapper

@log
def now():
    print('2024-6-1')

now()
# 输出:
# 调用 now() 函数
# 2024-6-1

@log 语法等同于 now = log(now)

2.2 保留原函数元信息

使用装饰器后,函数的 __name__ 等元信息会改变:

print(now.__name__)  # 输出: 'wrapper' (不是我们想要的)

为了解决这个问题,可以使用 functools.wraps

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f'调用 {func.__name__}() 函数')
        return func(*args, **kwargs)
    return wrapper

3. 带参数的装饰器

如果需要向装饰器传递参数,需要再嵌套一层函数:

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f'{text} {func.__name__}()')
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log('执行')
def now():
    print('2024-6-1')

now()
# 输出:
# 执行 now()
# 2024-6-1

这种三层嵌套的装饰器等价于 now = log('执行')(now)

4. 实践练习

4.1 计算函数执行时间

import time
import functools

def metric(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)*1000:.2f}ms')
        return result
    return wrapper

@metric
def fast(x, y):
    time.sleep(0.0012)
    return x + y

@metric
def slow(x, y, z):
    time.sleep(0.1234)
    return x * y * z

4.2 通用日志装饰器

实现一个既能作为简单装饰器,又能接受参数的装饰器:

import functools

def log(text=None):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if text:
                print(f'{text} {func.__name__}()')
            else:
                print(f'调用 {func.__name__}()')
            return func(*args, **kwargs)
        return wrapper
    
    # 如果直接 @log 调用,text 是函数对象
    if callable(text):
        func = text
        text = None
        return decorator(func)
    else:
        return decorator

# 两种使用方式
@log
def func1():
    pass

@log('执行')
def func2():
    pass

5. 类装饰器

装饰器也可以使用类来实现:

class Logger:
    def __init__(self, func):
        self.func = func
        functools.update_wrapper(self, func)
        
    def __call__(self, *args, **kwargs):
        print(f'调用 {self.func.__name__}()')
        return self.func(*args, **kwargs)

@Logger
def now():
    print('2024-6-1')

6. 最佳实践

  1. 始终使用 functools.wraps 保留原函数元信息
  2. 保持装饰器代码简洁,只添加必要的功能
  3. 考虑装饰器的可重用性
  4. 避免过度使用装饰器,以免降低代码可读性

7. 总结

装饰器是 Python 中非常强大的特性,它可以:

  • 在不修改原代码的情况下扩展功能
  • 实现横切关注点(如日志、计时、权限检查等)
  • 使代码更加模块化和可重用

掌握装饰器能让你写出更优雅、更 Pythonic 的代码。