Python 装饰器:@decorator 语法与函数包装
Python 的装饰器(decorator)是一种让你在不修改原函数代码的前提下,动态地给函数“加功能”的机制。最常见的表现形式就是函数定义前那一行 @xxx。它本质上是一个接收函数作为参数、并返回一个新函数的高阶函数。
理解装饰器的基本原理
-
定义一个普通函数
def greet(): print("Hello!") -
手动“包装”这个函数
创建一个新函数enhanced_greet,让它在调用greet前后加上额外操作:def add_logging(func): def wrapper(): print("Calling function:", func.__name__) func() print("Function finished.") return wrapper enhanced_greet = add_logging(greet) enhanced_greet()输出:
Calling function: greet Hello! Function finished. -
用
@语法糖简化写法
上面的enhanced_greet = add_logging(greet)可以直接写在函数定义上方:@add_logging def greet(): print("Hello!")这等价于先定义
greet,再执行greet = add_logging(greet)。@只是语法糖,核心仍是函数替换。
编写能处理任意参数的装饰器
上面的 wrapper 函数没有参数,无法用于带参数的函数。要让装饰器通用,必须使用 *args 和 **kwargs:
def add_logging(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
@add_logging
def add(a, b):
return a + b
add(3, 5)
输出:
Calling add with args=(3, 5), kwargs={}
add returned: 8
关键点:wrapper 必须接受任意参数,并原样传给被装饰的函数,同时返回其结果。
保留原函数元信息
使用上述装饰器后,greet.__name__ 会变成 "wrapper",这会干扰调试和文档工具。解决方法是使用 functools.wraps:
from functools import wraps
def add_logging(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@add_logging
def greet():
"""Say hello."""
print("Hello!")
print(greet.__name__) # 输出: greet
print(greet.__doc__) # 输出: Say hello.
务必在 wrapper 上加 @wraps(func),否则原函数的名称、文档字符串等元数据会丢失。
带参数的装饰器
有时需要给装饰器本身传参,比如指定日志级别。这时需要三层嵌套:
from functools import wraps
def log_with_level(level):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[{level}] Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@log_with_level("DEBUG")
def fetch_data():
return "data"
fetch_data()
输出:
[DEBUG] Calling fetch_data
执行顺序:
- 先调用
log_with_level("DEBUG"),返回decorator函数。 - 再用
@decorator装饰fetch_data,即fetch_data = decorator(fetch_data)。
常见用途与内置装饰器
1. @property:将方法转为属性
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return 3.14 * self._radius ** 2
c = Circle(2)
print(c.area) # 直接访问,无需 c.area()
2. @staticmethod 和 @classmethod
class MathUtils:
@staticmethod
def add(x, y):
return x + y
@classmethod
def from_string(cls, s):
x, y = map(int, s.split(','))
return cls.add(x, y)
print(MathUtils.add(1, 2)) # 3
print(MathUtils.from_string("3,4")) # 7
3. 自定义缓存装饰器
from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(10)) # 高效计算,避免重复递归
装饰器叠加与执行顺序
多个装饰器可以叠加使用,执行顺序从下到上:
def bold(func):
@wraps(func)
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold
@italic
def text():
return "Hello"
print(text()) # 输出: <b><i>Hello</i></b>
等价于:text = bold(italic(text))。最靠近函数定义的装饰器最先执行。
注意事项与陷阱
-
装饰器在模块导入时立即执行
被装饰的函数在定义时就会被替换,不是在调用时才生效。 -
避免在装饰器中修改原函数行为逻辑
装饰器应只添加辅助功能(如日志、计时),不要改变核心逻辑,否则难以调试。 -
带参数装饰器必须返回 decorator 函数
如果忘记返回内层函数,会导致@xxx()后面接的不是函数,引发TypeError。 -
类也可以作为装饰器
只需实现__call__方法:class CallCounter: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 return self.func(*args, **kwargs) @CallCounter def say_hi(): print("Hi") say_hi() print(say_hi.count) # 1
装饰器是 Python 中实现横切关注点(如日志、权限、缓存)的优雅方式。掌握其原理后,你可以写出更简洁、可复用的代码。

暂无评论,快来抢沙发吧!