第一次在Python代码中看到@符号时,我以为是某种特殊运算符。实际上这是Python独有的装饰器语法糖(Decorator Syntax Sugar),它让函数包装变得像给礼物系丝带一样优雅。装饰器的本质是一个高阶函数——接收函数作为参数并返回新函数的函数。
装饰器在Python 2.4版本引入,最初用于简化方法定义(如@classmethod)。如今它已成为框架开发的核心工具,Django的路由配置、Flask的路由声明都重度依赖装饰器语法。理解这个语法糖,等于拿到了Python进阶开发的钥匙。
Python中函数是一等公民(First-class citizen)。这意味着:
python复制def shout(text):
return text.upper()
# 函数作为对象赋值
yell = shout
print(yell('hello')) # 输出: HELLO
这种特性使得"函数加工厂"成为可能——我们编写一个函数,它接收旧函数,返回增强版新函数。
假设我们需要给函数添加执行计时功能:
python复制import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # 执行原函数
end = time.time()
print(f"{func.__name__} executed in {end-start:.4f}s")
return result
return wrapper
def heavy_calculation():
time.sleep(2)
# 手动包装函数
timed_heavy = timer(heavy_calculation)
timed_heavy() # 输出执行时间
这种包装方式虽然有效,但每次都需要显式调用timer()来包装目标函数,不够直观。
@符号的出现让装饰变得优雅:
python复制@timer
def heavy_calculation():
time.sleep(2)
# 直接调用已被装饰的函数
heavy_calculation()
Python解释器遇到@timer时,会自动执行heavy_calculation = timer(heavy_calculation)。这个语法糖让装饰器就像给函数贴标签一样简单。
有时我们需要装饰器本身接收参数。这时需要构造"装饰器工厂"——返回装饰器的函数:
python复制def repeat(n):
"""执行被装饰函数n次"""
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello {name}")
greet("Alice") # 打印3次问候
注意这里的三层嵌套结构:
装饰器也可以是一个类,只要实现__call__方法:
python复制class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"Calling {self.func.__name__}")
return self.func(*args, **kwargs)
@Logger
def add(a, b):
return a + b
print(add(2, 3)) # 先打印调用日志,再返回结果
类装饰器更适合需要维护状态的装饰逻辑,比如实现调用计数、缓存等复杂功能。
多个装饰器可以叠加使用,执行顺序从下往上:
python复制@decorator1
@decorator2
@decorator3
def func():
pass
# 等价于
func = decorator1(decorator2(decorator3(func)))
实际案例:Web框架中常见的路由+权限检查组合:
python复制@app.route('/admin')
@login_required
@admin_required
def admin_panel():
return render_template('admin.html')
直接使用装饰器会导致原函数的__name__、__doc__等元信息丢失:
python复制@timer
def countdown(n):
"""倒计时函数"""
while n > 0:
n -= 1
print(countdown.__name__) # 输出: wrapper
print(countdown.__doc__) # 输出: None
解决方法:使用functools.wraps装饰器
python复制from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
# ...原有逻辑...
return wrapper
被装饰的函数在traceback中会显示包装器信息,可能增加调试难度。可以通过以下方式改进:
python复制import inspect
def debug_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
print(f"Caller: {inspect.stack()[1].function}")
return func(*args, **kwargs)
return wrapper
装饰器会引入额外的函数调用开销。在性能敏感的循环中,可以考虑:
python复制# 不推荐:每次方法调用都执行装饰逻辑
class MyClass:
@timer
def method(self):
pass
# 更好:只在类实例化时执行一次装饰逻辑
@timer
class MyClass:
def method(self):
pass
@property系列装饰器用于创建属性访问控制:
python复制class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius must be positive")
self._radius = value
@radius.deleter
def radius(self):
print("Deleting radius")
del self._radius
functools模块提供多个实用装饰器:
python复制from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
contextlib.contextmanager可以将生成器函数转换为上下文管理器:
python复制from contextlib import contextmanager
@contextmanager
def managed_file(name):
try:
f = open(name, 'w')
yield f
finally:
f.close()
with managed_file('hello.txt') as f:
f.write('Hello, world!')
装饰器模式的核心价值在于运行时动态增强功能,而不需要修改原始代码。这在以下场景特别有用:
python复制def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=5)
def unreliable_api_call():
# 可能失败的API调用
pass
装饰器实质上是面向切面编程(AOP)在Python中的实现。通过装饰器,我们可以将横切关注点(如日志、事务)与业务逻辑分离:
python复制def transaction(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("BEGIN TRANSACTION")
try:
result = func(*args, **kwargs)
print("COMMIT")
return result
except Exception as e:
print("ROLLBACK")
raise e
return wrapper
@transaction
def transfer_money(from_acc, to_acc, amount):
# 资金转账业务逻辑
pass
通过装饰器组合可以实现复杂的功能管道:
python复制def compose(*decorators):
def decorator(func):
for d in reversed(decorators):
func = d(func)
return func
return decorator
@compose(log_call, validate_input, retry(3))
def critical_operation(data):
# 关键业务操作
pass
这种设计模式在Web中间件、数据处理流水线等场景非常实用。