Python 装饰器完整指南
Python 装饰器是 Python 中一个非常强大的功能,它是通过函数来增强或修改其他函数的行为。简而言之,装饰器是一个包装器,可以用来"装饰"一个函数,使其在不修改源代码的情况下,增加额外的功能。装饰器在很多 Python 框架中都有广泛应用,比如 Django、Flask 等。
1. 装饰器的概念
装饰器本质上是一个函数,它接受另一个函数作为参数,并返回一个新的函数。通过这种方式,装饰器能够在函数执行前后加入额外的功能,而不改变原函数的代码。
1.1. 装饰器的基本语法
1.1.1. 装饰器的函数结构
python
def decorator(func):
def wrapper():
print("Before function call")
func() # 调用原函数
print("After function call")
return wrapper
decorator
是装饰器函数,接收一个函数func
作为参数。wrapper
是一个内部函数,它在执行原函数前后加入额外的行为。
1.1.2. 使用装饰器
python
@decorator # 装饰器语法
def hello():
print("Hello, world!")
hello()
这段代码等同于:
python
def hello():
print("Hello, world!")
hello = decorator(hello)
hello()
1.1.3. 装饰器的执行流程
- 当你使用
@decorator
语法时,Python 会自动调用decorator(hello)
,并将hello
作为参数传递给decorator
。 decorator
返回一个新的函数wrapper
,然后将原函数hello
替换为wrapper
。- 当你调用
hello()
时,实际上调用的是wrapper()
,而wrapper
会先执行一些附加的操作,再调用原函数hello()
。
1.2. 保留原函数元信息 - functools.wraps
装饰器会改变函数的元信息(如函数名、文档字符串等),使用 @functools.wraps
可以保留这些信息。
python
import functools
# 不使用 functools.wraps 的问题
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@bad_decorator
def example_func():
"""This is an example function."""
pass
print(example_func.__name__) # wrapper (不是原函数名)
print(example_func.__doc__) # None (丢失了文档字符串)
# 使用 functools.wraps 的正确做法
def good_decorator(func):
@functools.wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@good_decorator
def example_func2():
"""This is an example function."""
pass
print(example_func2.__name__) # example_func2
print(example_func2.__doc__) # This is an example function.
1.3. 多个装饰器的执行顺序
当一个函数被多个装饰器装饰时,执行顺序是从内到外(从下到上)。
- 设置时的装饰顺序(从下到上)
- 执行时的调用顺序(从上到下)
python
def decorator_one(func):
print("Decorator One - Setup")
def wrapper(*args, **kwargs):
print("Decorator One - Before")
result = func(*args, **kwargs)
print("Decorator One - After")
return result
return wrapper
def decorator_two(func):
print("Decorator Two - Setup")
def wrapper(*args, **kwargs):
print("Decorator Two - Before")
result = func(*args, **kwargs)
print("Decorator Two - After")
return result
return wrapper
@decorator_one
@decorator_two
def hello():
print("Hello World")
# 设置时的装饰顺序(从下到上):
# 先执行:hello = decorator_two(hello)
# 后执行:hello = decorator_one(decorator_two(hello))
# 因此输出:
# Decorator Two - Setup
# Decorator One - Setup
hello()
# 执行时的调用顺序(从上到下):
# Decorator One - Before
# Decorator Two - Before
# Hello World
# Decorator Two - After
# Decorator One - After
2. 常见应用场景
2.1. 日志记录(也可以用来调试输出一些值)
装饰器可以用来记录函数执行的日志。
python
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with arguments {args} and keyword arguments {kwargs}")
return func(*args, **kwargs)
return wrapper
@log
def add(a, b):
return a + b
add(3, 5)
输出:
plain
Calling add with arguments (3, 5) and keyword arguments {}
8
2.2. 权限验证
你可以使用装饰器来验证用户是否有权限访问某些功能。
python
import functools
def has_permission(): # 实际需要替换验证逻辑
return True
def require_permission(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not has_permission(): # 权限检查函数
print("Permission denied")
return
return func(*args, **kwargs)
return wrapper
@require_permission
def delete_item(item):
print(f"Deleting {item}")
delete_item("file.txt")
2.3. 性能计时
装饰器可用于函数执行时间的统计和分析,通过记录函数调用前后的时间戳来计算执行耗时。
python
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.4f} seconds to execute")
return result
return wrapper
@timer
def long_running_task():
time.sleep(2)
long_running_task()
输出:
plain
long_running_task took 2.0001 seconds to execute
3. 装饰器的进阶概念
3.1. 带参数的装饰器
- 自定义mcp框架的时候使用这个可以方便的实现注册工具函数!
- 需要注意的是外层函数可以接受其它参数,但是func必须是有装饰器来接受,这里就是decorator
有时你需要给装饰器传递参数。你可以通过嵌套函数来实现这一点。
python
import functools
def repeat(n):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello!")
say_hello()
输出:
plain
Hello!
Hello!
Hello!
3.2. 装饰器的返回值
装饰器可以返回任何类型的数据,而不仅仅是原函数的返回值。你可以让装饰器改变函数的返回值。
- 在这里是装饰器内部直接调用了我们的func然后对返回值修改
python
import functools
def multiply_by_two(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
@multiply_by_two
def get_number():
return 5
print(get_number()) # 10
3.3. 类装饰器
- 这里的func参数是直接在类的__init__中接受的参数!
装饰器不仅可以是函数,也可以是类。类装饰器通过实现 __call__
方法来工作。
python
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} has been called {self.count} times")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # say_hello has been called 1 times
say_hello() # say_hello has been called 2 times
3.4. 装饰类的装饰器
装饰器不仅可以装饰函数,还可以装饰类。这里使用类方法也可以实现类似的效果!
python
def add_method(cls):
def new_method(self):
print(f"This is a dynamically added method to {cls.__name__}")
cls.dynamic_method = new_method
return cls
@add_method
class MyClass:
def __init__(self, name):
self.name = name
obj = MyClass("test")
obj.dynamic_method() # This is a dynamically added method to MyClass
3.5. 装饰实例方法时的注意事项
- 装饰类的实例方法的时候需要self参数
python
import functools
def method_decorator(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs): # 注意要包含 self 参数
print(f"Decorating method {func.__name__} of {self.__class__.__name__}")
return func(self, *args, **kwargs)
return wrapper
class MyClass:
@method_decorator
def my_method(self):
print("Original method")
obj = MyClass()
obj.my_method()
输出:
plain
Decorating method my_method of MyClass
Original method
4. 装饰器的实际使用
- Flask 路由装饰器:
@app.route('/path')
用于将 URL 映射到视图函数。 - Django 视图装饰器:
@login_required
装饰器用于在视图函数前验证用户是否已登录。 - 缓存装饰器:用来缓存函数的返回值,避免重复计算。
- 重试装饰器:在函数执行失败时自动重试。
- 异步装饰器:用于处理异步函数的装饰。