在现代 Python 高并发架构(如 FastAPI、Tornado、异步爬虫框架)中,async/await 已经成为标配。然而,传统的同步装饰器和代理如果直接套用在协程函数上,往往会导致事件循环阻塞 或类型断言失败。
1. 兼容同步与异步的万能装饰器
一个优秀的现代化装饰器,必须具备"双模"识别能力------既能装饰普通函数,也能装饰协程函数(Coroutine Function)。
实战案例:支持 Async/Sync 的通用耗时统计装饰器
import time
import asyncio
from functools import wraps
def monitor_performance(func):
"""自适应同步与异步的性能监控装饰器"""
if asyncio.iscoroutinefunction(func):
@wraps(func)
async def async_wrapper(*args, **kwargs):
start_time = time.perf_counter()
try:
result = await func(*args, **kwargs)
return result
finally:
elapsed = time.perf_counter() - start_time
print(f"[异步监控] 协程 {func.__name__} 耗时: {elapsed:.4f} 秒")
return async_wrapper
else:
@wraps(func)
def sync_wrapper(*args, **kwargs):
start_time = time.perf_counter()
try:
result = func(*args, **kwargs)
return result
finally:
elapsed = time.perf_counter() - start_time
print(f"[同步监控] 函数 {func.__name__} 耗时: {elapsed:.4f} 秒")
return sync_wrapper
# 使用示例
@monitor_performance
async def fetch_data_from_remote(url: str):
await asyncio.sleep(1) # 模拟网络 IO
return f"Response of {url}"
@monitor_performance
def heavy_cpu_task(n: int):
return sum(i * i for i in range(n))
# 测试运行
async def main():
print(await fetch_data_from_remote("https://api.csdn.net"))
print(heavy_cpu_task(1000000))
# 运行异步主函数
asyncio.run(main())
架构避坑指南 :在编写代理模式处理异步方法时,切记要在
__getattr__中判断目标方法是否为协程,并显式通过async def返回包装后的协程对象,否则在调用时会抛出TypeError: object NoneType can't be used in 'await' expression异常。
七、 架构师视角:何时选择装饰器?何时选择代理模式?
在面对具体业务场景时,我们究竟该用装饰器还是代理模式?可以通过下表进行快速权衡:
| 维度 | 装饰器模式 (Decorator) | 代理模式 (Proxy) |
|---|---|---|
| 关注点 | 侧重于给函数或类动态增加切面功能(AOP),如日志、鉴权、缓存。 | 侧重于控制对某个对象的全生命周期访问,如延迟加载、远程代理、访问权限控制。 |
| 粒度 | 通常针对单一函数、方法或整个类。 | 通常针对整个对象的所有属性和方法行为进行整体拦截。 |
| 实现机制 | Python 原生语法糖 @decorator,基于高阶函数或类闭包。 |
多依赖魔法方法(如 __getattr__、__getattribute__)进行属性重定向。 |
| 典型应用场景 | Web 路由注册、API 权限校验、性能埋点。 | ORM 延迟加载、RPC 客户端存根、保护敏感对象。 |
八、 写在最后
从经典设计模式到现代 Python 的演进,我们不难发现:语言越强大,模式越轻量。
在 Java 中可能需要十几行甚至上百行才能搞定的动态代理和切面逻辑,在 Python 中借助一等函数、闭包以及强大的元编程(Meta-programming)魔法方法,往往寥寥数行即可实现。然而,能力越大责任越大,过度使用"动态拦截"和"魔法方法"可能会降低代码的可读性和静态类型检查(如 Mypy)的友好度。
在实际开发中,保持"显式优于隐式"的 Pythonic 哲学,结合业务场景权衡选择,才能让代码既有现代化的优雅,又具备工业级的稳健。