Python 装饰器
一、装饰器是什么
装饰器本质上是一个“接收函数,返回新函数”的函数。
它常用于在不修改原函数代码的前提下,给函数额外增加能力,例如:
- 打印日志
- 统计耗时
- 权限校验
- 参数检查
- 缓存结果
- 统一异常处理
装饰器语法:
@decorator
def func():
pass等价于:
def func():
pass
func = decorator(func)二、函数也是对象
理解装饰器前,先理解:函数可以像普通变量一样传递。
def hello():
print("hello")
fn = hello
fn()
# output:hello函数也可以作为另一个函数的参数:
def call_func(func):
func()
def hello():
print("hello")
call_func(hello)
# output:hello三、最简单的装饰器
def trace(func):
def wrapper():
print("函数调用前")
func()
print("函数调用后")
return wrapper
@trace
def hello():
print("hello")
hello()执行过程:
@trace
def hello():
...
等价于:
hello = trace(hello)所以最后调用的 hello(),其实已经不是原来的 hello(),而是 trace 返回的 wrapper()。
四、装饰带参数的函数
如果原函数有参数,内部的 wrapper 也要接收参数。
def trace(func):
def wrapper(name):
print("函数调用前")
result = func(name)
print("函数调用后")
return result
return wrapper
@trace
def hello(name):
return f"hello {name}"
print(hello("Tom"))更通用的写法是使用 *args 和 **kwargs:
def trace(func):
def wrapper(*args, **kwargs):
print(f"开始调用: {func.__name__}")
result = func(*args, **kwargs)
print(f"结束调用: {func.__name__}")
return result
return wrapper这样无论原函数有多少参数,都可以正常转发。
五、保留原函数信息
普通装饰器会让函数名、文档等元信息变成 wrapper。
推荐使用 functools.wraps:
from functools import wraps
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"call {func.__name__}")
return func(*args, **kwargs)
return wrapper这是写装饰器时很重要的习惯。
六、带参数的装饰器
有时装饰器本身也需要参数,例如控制日志等级、重复次数、权限角色。
带参数装饰器有三层函数:
from functools import wraps
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def hello(name):
print(f"hello {name}")
hello("Tom")执行关系:
@repeat(3)
def hello(...):
...
等价于:
hello = repeat(3)(hello)七、多个装饰器叠加
@decorator_a
@decorator_b
def func():
pass等价于:
func = decorator_a(decorator_b(func))距离函数最近的装饰器先包裹,调用时最外层装饰器先执行。
from functools import wraps
def outer(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("outer before")
result = func(*args, **kwargs)
print("outer after")
return result
return wrapper
def inner(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("inner before")
result = func(*args, **kwargs)
print("inner after")
return result
return wrapper
@outer
@inner
def hello():
print("hello")
hello()输出顺序:
outer before
inner before
hello
inner after
outer after八、类装饰器
类也可以实现装饰器,核心是 __call__ 方法。
from functools import wraps
class Trace:
def __init__(self, func):
self.func = func
wraps(func)(self)
def __call__(self, *args, **kwargs):
print(f"call {self.func.__name__}")
return self.func(*args, **kwargs)
@Trace
def add(a, b):
return a + b
print(add(1, 2))函数装饰器更常见,类装饰器适合需要保存状态的场景。
九、常见内置装饰器
property
把方法包装成属性访问。
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@property
def passed(self):
return self.score >= 60
student = Student("Tom", 80)
print(student.passed)staticmethod
静态方法不接收 self,通常用于和类相关但不依赖对象状态的工具函数。
class MathTool:
@staticmethod
def add(a, b):
return a + b
print(MathTool.add(1, 2))classmethod
类方法接收 cls,常用于工厂方法。
class User:
def __init__(self, name):
self.name = name
@classmethod
def from_dict(cls, data):
return cls(data["name"])
user = User.from_dict({"name": "Tom"})
print(user.name)十、常见应用案例
计时器
from functools import wraps
from time import perf_counter
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = perf_counter()
result = func(*args, **kwargs)
end = perf_counter()
print(f"{func.__name__} cost {end - start:.6f}s")
return result
return wrapper简单权限检查
from functools import wraps
def require_role(role):
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user.get("role") != role:
raise PermissionError("permission denied")
return func(user, *args, **kwargs)
return wrapper
return decorator十一、容易踩的坑
忘记 return 原函数结果
错误写法:
def trace(func):
def wrapper(*args, **kwargs):
print("call")
func(*args, **kwargs)
return wrapper如果原函数有返回值,这种写法会导致返回 None。
正确写法:
def trace(func):
def wrapper(*args, **kwargs):
print("call")
return func(*args, **kwargs)
return wrapper忘记使用 wraps
from functools import wraps写通用装饰器时,建议总是加 @wraps(func),避免函数名、文档字符串、类型提示等信息丢失。
wrapper 参数写死
如果装饰器只写:
def wrapper():
return func()那么它只能装饰无参数函数。通用装饰器应写成:
def wrapper(*args, **kwargs):
return func(*args, **kwargs)十二、学习建议
- 先理解“函数可以作为参数和返回值”。
- 再理解
func = decorator(func)。 - 普通装饰器熟练后,再学带参数装饰器。
- 写实际代码时优先使用
functools.wraps。 - 装饰器不要写得过重,否则会隐藏真实逻辑。
十三、案例
py装饰器案例
Python 装饰器案例
下面这段代码可以直接复制到一个
.py文件中运行,覆盖普通装饰器、计时装饰器、带参数装饰器、权限检查和内置装饰器。指向原始笔记的链接 from functools import wraps from time import perf_counter, sleep def trace(func): @wraps(func) def wrapper(*args, **kwargs): print(f"call {func.__name__}") return func(*args, **kwargs) return wrapper def timer(func): @wraps(func) def wrapper(*args, **kwargs): start = perf_counter() result = func(*args, **kwargs) end = perf_counter() print(f"{func.__name__} cost {end - start:.6f}s") return result return wrapper def repeat(times): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): result = None for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator def require_role(role): def decorator(func): @wraps(func) def wrapper(user, *args, **kwargs): if user.get("role") != role: raise PermissionError("permission denied") return func(user, *args, **kwargs) return wrapper return decorator @trace def add(a, b): return a + b @timer def slow_task(): sleep(0.01) return "done" @repeat(3) def say_hi(name): print(f"hi {name}") @require_role("admin") def delete_post(user, post_id): return f"user {user['name']} deleted post {post_id}" class Student: def __init__(self, name, score): self.name = name self.score = score @property def passed(self): return self.score >= 60 @classmethod def from_dict(cls, data): return cls(data["name"], data["score"]) @staticmethod def is_valid_score(score): return 0 <= score <= 100 def main(): print(add(1, 2)) print(slow_task()) say_hi("Tom") admin = {"name": "Lucy", "role": "admin"} print(delete_post(admin, 1001)) student = Student.from_dict({"name": "Jack", "score": 72}) print(student.name, student.passed) print(Student.is_valid_score(101)) if __name__ == "__main__": main()