
装饰器是 Python 中修改或增强函数行为 的一种设计模式,它可以在不修改原函数代码 的情况下,为函数添加额外功能。装饰器本质上是一个高阶函数,接收一个函数作为参数,返回一个新的函数。
一、装饰器
1. 为什么需要装饰器?
# 原始需求:计算函数执行时间
# 问题:每个函数都要重复写计时代码
def func1():
start = time.time()
# ... 函数逻辑
end = time.time()
print(f"耗时: {end - start}")
def func2():
start = time.time()
# ... 函数逻辑
end = time.time()
print(f"耗时: {end - start}")
# 装饰器解决方案:复用计时逻辑
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 耗时: {end - start:.2f}s")
return result
return wrapper
@timer
def func1():
pass
@timer
def func2():
pass
2. 装饰器的本质
# 装饰器语法糖
@decorator
def func():
pass
# 等价于
func = decorator(func)
二、函数装饰器
1. 无参数装饰器
def logger(func):
"""记录函数调用的装饰器"""
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
print(f"参数: args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"返回值: {result}")
return result
return wrapper
@logger
def add(a, b):
return a + b
add(3, 5)
# 输出:
# 调用函数: add
# 参数: args=(3, 5), kwargs={}
# 返回值: 8
2. 带参数的装饰器
def repeat(times):
"""重复执行函数的装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
results = []
for i in range(times):
print(f"第 {i+1} 次执行")
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(3)
def greet(name):
return f"Hello, {name}"
result = greet("Alice")
print(result) # ['Hello, Alice', 'Hello, Alice', 'Hello, Alice']
3. 多层装饰器
def bold(func):
def wrapper(*args, **kwargs):
return f"<b>{func(*args, **kwargs)}</b>"
return wrapper
def italic(func):
def wrapper(*args, **kwargs):
return f"<i>{func(*args, **kwargs)}</i>"
return wrapper
@bold
@italic
def greet(name):
return f"Hello, {name}"
print(greet("Alice")) # <b><i>Hello, Alice</i></b>
# 执行顺序:从下往上
# greet = bold(italic(greet))
三、类装饰器
1. 类作为装饰器
class CountCalls:
"""统计函数调用次数的装饰器"""
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"调用次数: {self.count}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # 调用次数: 1 \n Hello!
say_hello() # 调用次数: 2 \n Hello!
2. 带参数的类装饰器
class Repeat:
def __init__(self, times):
self.times = times
def __call__(self, func):
def wrapper(*args, **kwargs):
results = []
for i in range(self.times):
results.append(func(*args, **kwargs))
return results
return wrapper
@Repeat(3)
def greet(name):
return f"Hello, {name}"
print(greet("Alice")) # ['Hello, Alice', 'Hello, Alice', 'Hello, Alice']
四、常用内置装饰器
1. @staticmethod 和 @classmethod
class MyClass:
class_var = "类变量"
@staticmethod
def static_method():
"""静态方法:不需要 self 或 cls"""
return "静态方法"
@classmethod
def class_method(cls):
"""类方法:第一个参数是类本身"""
return f"类方法,访问 {cls.class_var}"
def instance_method(self):
"""实例方法:第一个参数是实例"""
return "实例方法"
print(MyClass.static_method()) # 静态方法
print(MyClass.class_method()) # 类方法,访问 类变量
2. @property
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""getter"""
return self._radius
@radius.setter
def radius(self, value):
"""setter"""
if value <= 0:
raise ValueError("半径必须大于0")
self._radius = value
@property
def area(self):
"""只读属性"""
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius) # 5
c.radius = 10
print(c.area) # 314.159
3. @functools.wraps
保留原函数的元数据(名称、文档字符串等)。
import functools
def my_decorator(func):
@functools.wraps(func) # 保留 func 的元信息
def wrapper(*args, **kwargs):
"""包装函数"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet(name):
"""问候函数"""
return f"Hello, {name}"
print(greet.__name__) # greet(有 @wraps)
print(greet.__doc__) # 问候函数(有 @wraps)
# 如果没有 @wraps,会输出 wrapper 和 包装函数
五、装饰器的执行顺序
def decorator_a(func):
print("装饰器 A 开始")
def wrapper(*args, **kwargs):
print("调用 A")
return func(*args, **kwargs)
print("装饰器 A 结束")
return wrapper
def decorator_b(func):
print("装饰器 B 开始")
def wrapper(*args, **kwargs):
print("调用 B")
return func(*args, **kwargs)
print("装饰器 B 结束")
return wrapper
@decorator_a
@decorator_b
def my_func():
print("原函数")
my_func()
装饰器 B 开始
装饰器 B 结束
装饰器 A 开始
装饰器 A 结束
调用 A
调用 B
原函数
执行顺序:
-
装饰阶段 :从下往上(
decorator_b先执行) -
调用阶段 :从上往下(
wrapper_a→wrapper_b→ 原函数)
核心要点:
-
装饰器本质是高阶函数:接收函数,返回函数
-
@decorator等价于func = decorator(func) -
多个装饰器:装饰时从下往上 ,调用时从上往下
-
总是用
@functools.wraps保留原函数元信息 -
类装饰器通过
__call__方法实现