markdown复制## 1. 装饰器语法糖@的本质解析
在Python中看到`@`符号时,90%的情况你遇到的是装饰器语法糖。这个看似简单的符号背后,实际上是Python语言设计中最优雅的特性之一。装饰器的本质是函数式编程中的高阶函数应用,它允许在不修改原函数代码的前提下,为函数动态添加功能。
装饰器的标准写法本应是:
```python
def decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
def say_hello():
print("Hello!")
say_hello = decorator(say_hello) # 手动装饰
而使用@语法糖后,代码简化为:
python复制@decorator
def say_hello():
print("Hello!")
关键理解:装饰器在函数定义时立即执行,而不是在函数调用时。这个时间差常常是初学者困惑的根源。
python复制import time
def timing_decorator(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
@timing_decorator
def data_processing():
# 模拟耗时操作
time.sleep(1.5)
python复制def admin_required(func):
def wrapper(user, *args, **kwargs):
if not user.is_admin:
raise PermissionError("Admin privileges required")
return func(user, *args, **kwargs)
return wrapper
@admin_required
def delete_database(user):
print("Database deleted!")
python复制def validate_types(*arg_types, **kwarg_types):
def decorator(func):
def wrapper(*args, **kwargs):
# 参数类型检查逻辑
return func(*args, **kwargs)
return wrapper
return decorator
@validate_types(arg1=int, arg2=str)
def process_data(arg1, arg2):
print(f"Processing {arg1} and {arg2}")
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)
装饰器本身也可以接受参数,形成两层嵌套:
python复制def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Hello {name}!")
装饰器不仅可以装饰函数,还能装饰类:
python复制def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class DatabaseConnection:
pass
多个装饰器堆叠时,执行顺序是从下往上:
python复制@decorator1
@decorator2
@decorator3
def func():
pass
# 等价于 func = decorator1(decorator2(decorator3(func)))
常见陷阱:装饰器会改变函数的
__name__等元信息,使用functools.wraps可以保留原函数属性:
python复制from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
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 cannot be negative")
self._radius = value
python复制class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def from_string(cls, date_string):
year, month, day = map(int, date_string.split('-'))
return cls(year, month, day)
@staticmethod
def is_valid(date_string):
try:
year, month, day = map(int, date_string.split('-'))
return True
except:
return False
python复制from contextlib import contextmanager
@contextmanager
def open_file(path, mode):
try:
file = open(path, mode)
yield file
finally:
file.close()
# 使用方式
with open_file('test.txt', 'w') as f:
f.write('Hello')
当装饰的函数出现异常时,堆栈跟踪会显示装饰器的wrapper函数,这使得调试变得困难。解决方法:
functools.wraps保留原函数元信息inspect模块获取更多调用信息装饰器会引入额外的函数调用开销,在性能敏感的代码中需要注意:
lru_cache缓存装饰器的计算结果装饰异步函数时需要特别注意:
python复制def async_timing_decorator(func):
async def wrapper(*args, **kwargs):
start = time.time()
result = await func(*args, **kwargs)
end = time.time()
print(f"Async {func.__name__} took {end-start:.2f}s")
return result
return wrapper
@async_timing_decorator
async def fetch_data():
await asyncio.sleep(1)
return "data"
在大型项目中,装饰器常用于实现横切关注点:
示例:实现一个重试装饰器
python复制import random
from functools import wraps
from time import sleep
def retry(max_attempts=3, delay=1, exceptions=(Exception,)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempt = 0
while attempt < max_attempts:
try:
return func(*args, **kwargs)
except exceptions as e:
attempt += 1
if attempt == max_attempts:
raise
sleep(delay * (1 + random.random()))
return wrapper
return decorator
@retry(max_attempts=5, exceptions=(ConnectionError,))
def call_unstable_api():
# 模拟不稳定的API调用
if random.random() > 0.3:
raise ConnectionError("API failed")
return "Success"
装饰器可以与描述符、元类等特性结合,实现更强大的元编程功能:
python复制def implements(interface):
def decorator(cls):
for method in interface.__abstractmethods__:
if not hasattr(cls, method):
raise TypeError(f"Class must implement {method}")
return cls
return decorator
from abc import ABC, abstractmethod
class ServiceInterface(ABC):
@abstractmethod
def start(self): pass
@abstractmethod
def stop(self): pass
@implements(ServiceInterface)
class MyService:
def start(self):
print("Service started")
def stop(self):
print("Service stopped")
python复制def register_plugin(plugin_type):
def decorator(cls):
cls._plugin_type = plugin_type
PluginRegistry.register(plugin_type, cls)
return cls
return decorator
@register_plugin("audio")
class AudioProcessor:
pass
@register_plugin("video")
class VideoProcessor:
pass
测试装饰器时需要同时验证:
示例测试用例:
python复制import unittest
class TestTimingDecorator(unittest.TestCase):
def test_timing_output(self):
from io import StringIO
import sys
@timing_decorator
def dummy():
pass
saved_stdout = sys.stdout
try:
out = StringIO()
sys.stdout = out
dummy()
output = out.getvalue()
self.assertIn("dummy executed", output)
finally:
sys.stdout = saved_stdout
当多个装饰器组合使用时,可以使用以下技巧调试:
inspect模块检查函数签名在需要极致性能的场景下,可以考虑以下替代方案:
python复制class Profiler:
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
return self.func(*args, **kwargs)
@Profiler
def expensive_operation():
pass
对于类型明确的函数,可以使用mypyc等工具将Python代码编译为C扩展,此时装饰器逻辑可以在编译时处理。
functools.wraps保留元信息最后分享一个实用技巧:在IPython或Jupyter中,可以使用??操作符查看被装饰函数的原始代码,这有助于理解复杂装饰器链的行为。
code复制