介绍
🧠 什么是 Python 装饰器?
装饰器(Decorator) 是一个函数,它可以在不修改原函数代码的前提下,增强或修改函数的行为。本质上,装饰器就是“函数的函数”。
它常用于:
• 代码复用(比如日志、性能统计、权限校验)
• AOP 编程思想(面向切面编程)
💡 常见应用场景
-
日志记录
-
性能测试
-
权限验证
-
缓存(如 LRU 缓存)
-
重试机制
-
Flask、Django 等框架中的路由注册、权限控制等
🧰 Python 内置装饰器示例
• @staticmethod
• @classmethod
• @property
• @functools.lru_cache
🧪 基本语法
def decorator(func):
def wrapper(*args, **kwargs): # wrapper 是包装函数
print("调用前")
result = func(*args, **kwargs)
print("调用后")
return result
return wrapper # 返回包装后的函数
@decorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
这个装饰器 等价于:
def say_hello(name):
print(f"Hello, {name}")
say_hello = decorator(say_hello)
>>>>>>>>>>>
调用前
Hello, Alice!
调用后
func只是 decorator(func) 里传进来的一个函数参数,你可以叫它 f、target_func、banana 都可以(当然为了可读性我们通常叫它 func 或 fn)。
重点:
原始函数 say_hello --> 被装饰器 decorator 包装 --> 得到新函数 wrapper --> 用 wrapper 替换原函数
执行阶段
是先执行装饰器,还是里面的函数?
在程序加载时,先执行装饰器(也就是装饰器函数 decorator(func)),
而在程序运行时,才执行被包裹的原始函数。
🧱 阶段一:定义阶段(加载 Python 文件时)
@decorator
def greet(name):
print(f"Hi, {name}")
其实它做的事情是:
# 把 greet 函数对象传进去,执行 decorator 函数
greet = decorator(greet)
这一步是 立即执行 decorator 函数,并返回一个 wrapper 函数,再赋值回 greet。
✅ 此时函数体还没有执行,只是函数被“装饰”了,变成了 wrapper。
def decorator(func):
print("装饰器被执行!") # 加载阶段运行
def wrapper(*args, **kwargs):
print("调用前")
result = func(*args, **kwargs)
print("调用后")
return result
return wrapper
@decorator
def greet(name):
print(f"Hi, {name}")
print("准备调用 greet 函数了")
greet("Tom")
>>>>>>>>>>
装饰器被执行!
准备调用 greet 函数了
调用前
Hi, Tom
调用后
时机 | 发生什么 |
---|---|
Python 加载函数时 | 执行装饰器 decorator(func),返回 wrapper |
调用函数时 | 执行 wrapper(*args, **kwargs),间接调用原始 func() |
进阶用法
多个装饰器
@decorator1
@decorator2
def my_func():
...
等价于:
my_func = decorator1(decorator2(my_func))
🛠 带参数的装饰器
def log(prefix):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"{prefix} 调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@log("DEBUG")
def run():
print("Running...")
run()
同步&异步装饰器
import asyncio
import functools
def async_compatible_decorator(func):
@functools.wraps(func)
def sync_wrapper(*args, **kwargs):
print("装饰器:调用前")
if asyncio.iscoroutinefunction(func):
# 如果是异步函数,返回一个协程函数
async def async_inner():
result = await func(*args, **kwargs)
print("装饰器:调用后")
return result
return async_inner()
else:
# 同步函数
result = func(*args, **kwargs)
print("装饰器:调用后")
return result
return sync_wrapper
测试使用用法:
@async_compatible_decorator
def say_hi(name):
print(f"Hi {name}")
@async_compatible_decorator
async def say_hi_async(name):
print(f"Hi async {name}")
say_hi("Tom") # 同步调用
asyncio.run(say_hi_async("Alice")) # 异步调用
🔍 为什么要这么写?
关键点 | 解释 |
---|---|
asyncio.iscoroutinefunction(func) | 判断函数是不是 async def |
async def async_inner(): await func(…) | 正确调用异步函数 |
同步函数走正常流程 | 兼容 def 写法 |
functools.wraps(func) | 保留原函数信息,避免装饰器污染函数名/文档 |
🚨一个同步的装饰器(def decorator(func)),怎么能装饰一个 async def 异步函数呢?
装饰器本身是同步的,它只在函数定义时执行。
而你真正要“同步 or 异步”,其实是你包装返回的 wrapper 函数的执行阶段。
这时候 my_decorator 是一个同步函数,它接受了一个异步函数对象作为参数,然后返回一个新函数(通常叫 wrapper)。
✅ 装饰器本身是同步的,这没问题,因为它只是拿到函数对象,并返回另一个函数而已。
问题 | 解释 |
---|---|
装饰器是同步的,可以装饰异步函数吗? | 可以,因为它只是返回包装函数,不执行原函数 |
同步装饰器包装异步函数安全吗? | 不安全,除非你返回的是 async def wrapper() 并正确 await |
解决方法 | 判断是否 async,再返回不同类型的 wrapper |