Appearance
32|装饰器
装饰器是在不修改原函数代码的前提下,给函数添加额外功能的机制。常见的用途有:计时、日志、重试、权限检查、缓存。
一、函数是一等对象
在 Python 中,函数和数字、字符串一样,可以赋值给变量、作为参数传递、作为返回值。
python
def greet(name):
return f"hello {name}"
say_hi = greet # 把函数赋给变量
say_hi("Alice") # 'hello Alice'函数作为参数:
python
def apply_twice(func, value):
return func(func(value))
apply_twice(lambda x: x * 2, 5) # 20函数作为返回值:
python
def make_multiplier(n):
def multiply(x):
return x * n
return multiply
triple = make_multiplier(3)
triple(5) # 15二、最简单的装饰器
装饰器本质上是一个接收函数、返回新函数的高阶函数:
python
def log_call(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} 返回 {result}")
return result
return wrapper
def add(a, b):
return a + b
# 手动应用装饰器
add = log_call(add)
add(2, 3)
# 调用 add
# add 返回 5三、@ 语法
@ 是应用装饰器的语法糖:
python
@log_call
def add(a, b):
return a + b
# 等价于:
# add = log_call(add)四、带参数的装饰器
装饰器本身需要参数时,要嵌套三层函数:
python
def retry(times=3):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except Exception as exc:
print(f"第 {attempt} 次失败: {exc}")
if attempt == times:
raise
return wrapper
return decorator
@retry(times=3)
def fetch_data():
...执行顺序:retry(times=3) 先调用,返回真正的装饰器 decorator,再用它包装 fetch_data。
五、保留元信息
装饰后的函数丢失了原函数的元信息:
python
@log_call
def add(a, b):
"""返回两数之和。"""
return a + b
print(add.__name__) # wrapper,不是 add
print(add.__doc__) # None,不是原 docstring用 functools.wraps 修复:
python
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def add(a, b):
"""返回两数之和。"""
return a + b
print(add.__name__) # add
print(add.__doc__) # 返回两数之和。@wraps(func) 把原函数的 __name__、__doc__ 等属性复制到 wrapper 上。
六、类装饰器
装饰器也可以用类实现:
python
class CountCalls:
def __init__(self, func):
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_hello():
print("hello")
say_hello() # 被调用了 1 次
say_hello() # 被调用了 2 次类装饰器适合需要维护状态的场景(如调用次数、缓存内容)。
七、内置装饰器
Python 提供了几个常用装饰器:
python
# @property:把方法变成属性
class Circle:
@property
def area(self):
return 3.14 * self.radius ** 2
# @staticmethod:静态方法,不需要 self
class Math:
@staticmethod
def add(a, b):
return a + b
# @classmethod:类方法,第一个参数是 cls
class Person:
count = 0
@classmethod
def create(cls, name):
cls.count += 1
return cls(name)记忆锚点:装饰器是接收函数、返回新函数的高阶函数;@ 是应用装饰器的语法糖;带参数的装饰器需要三层嵌套;@wraps(func) 保留原函数元信息;类装饰器通过 __call__ 实现,适合维护状态;@property、@staticmethod、@classmethod 是内置装饰器。