Python 中的装饰器(Decorators) 是一种强大且优雅的语法糖,它允许你在不修改原始函数或类定义的情况下,动态地给它们添加功能或修改它们的行为。你可以将装饰器想象成一个“包装器”,它在不改变被包装对象内容的前提下,为其增加新的特性。
什么是装饰器?
从本质上讲,装饰器是一个接受函数(或类)作为输入,并返回一个新函数(或新类)的可调用对象(callable)。这个新函数/类通常会包含原始函数/类的逻辑,并在此基础上添加一些额外的功能。
Python 中使用 @
符号来应用装饰器,它放在函数或类定义的上方。
# 定义一个装饰器函数
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func() # 调用原始函数
print("Something is happening after the function is called.")
return wrapper
@my_decorator # 使用 @ 语法糖应用装饰器
def say_hello():
print("Hello!")
say_hello()
输出:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
在这个例子中,my_decorator
就是一个装饰器。@my_decorator
放在 say_hello
函数上方,等同于执行了 say_hello = my_decorator(say_hello)
。
装饰器的工作原理
- 定义装饰器:你首先定义一个函数(或类,更高级的用法),它接受一个函数作为参数。
-
定义内部函数(Wrapper):在这个装饰器内部,你通常会定义一个嵌套函数(通常命名为
wrapper
),这个wrapper
函数会包含你想要添加的额外逻辑,并且会在某个地方调用原始函数。 -
返回 Wrapper:装饰器函数最后返回这个
wrapper
函数。 -
应用装饰器:当你使用
@decorator_name
语法时,Python 会自动将被装饰的函数作为参数传递给decorator_name
函数,然后用返回的wrapper
函数替换掉原始函数。
为什么使用装饰器?
装饰器提供了一种优雅的方式来实现以下目标:
- 代码重用:将通用的功能(如日志、性能计时、权限检查、缓存等)封装成装饰器,可以在多个函数或类中重复使用,避免代码重复。
- 关注点分离:将核心业务逻辑与非核心的横切关注点(cross-cutting concerns)分离。例如,一个函数只负责其核心计算,而日志记录则由装饰器处理。
-
提高可读性:通过简洁的
@
语法,代码的意图更加清晰。 - 不修改原始代码:你可以在不触碰原始函数或类定义的情况下,扩展或修改其行为。
装饰器的常见应用场景
- 日志记录(Logging):记录函数调用的时间、参数和返回值。
- 性能分析(Profiling):测量函数的执行时间。
- 权限检查(Authorization):验证用户是否有权访问某个函数或资源。
- 缓存(Caching):缓存函数的返回结果,避免重复计算。
- 输入验证(Input Validation):在函数执行前检查输入参数的有效性。
- 重试机制(Retries):当函数失败时自动重试。
- API 路由(API Routing):在 Web 框架中,如 Flask 和 Django,装饰器常用于将 URL 路径映射到视图函数。
带参数的函数装饰器
如果被装饰的函数需要参数,你的 wrapper
函数也必须能接收这些参数,并传递给原始函数。这通常通过 *args
和 **kwargs
来实现。
def log_arguments(func):
def wrapper(*args, **kwargs): # 接收任意位置参数和关键字参数
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
@log_arguments
def add(a, b):
return a + b
@log_arguments
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
add(10, 20)
greet("Alice", greeting="Hi")
带参数的装饰器
有些时候,你可能需要自定义装饰器的行为,这就需要装饰器本身接收参数。这通常通过一个三层嵌套的结构来实现。外层函数接收装饰器参数,然后返回真正的装饰器函数。
def repeat(num_times): # 第一层:接收装饰器参数
def decorator(func): # 第二层:接收被装饰的函数
def wrapper(*args, **kwargs): # 第三层:接收被装饰函数的参数
for _ in range(num_times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(num_times=3) # 调用装饰器,传入参数
def bark():
print("Woof!")
@repeat(num_times=2)
def greet_person(name):
print(f"Hello, {name}!")
bark()
print("---")
greet_person("Bob")
类装饰器
装饰器不仅可以装饰函数,还可以装饰类。此外,你也可以使用类来作为装饰器,如果这个类实现了 __call__
方法。
# 类作为装饰器
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs): # 使类的实例可调用
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hi():
print("Hi!")
say_hi()
say_hi()
@functools.wraps
在使用装饰器时,一个常见的问题是,装饰器会替换掉原始函数的元数据(如 __name__
、__doc__
)。为了解决这个问题,Python 提供了 functools.wraps
装饰器。
import functools
def my_logging_decorator(func):
@functools.wraps(func) # 使用 wraps 装饰器
def wrapper(*args, **kwargs):
"""This is the wrapper function's docstring."""
print(f"Logging call to {func.__name__}")
return func(*args, **kwargs)
return wrapper
@my_logging_decorator
def my_function():
"""This is my_function's docstring."""
pass
print(my_function.__name__) # 输出: my_function
print(my_function.__doc__) # 输出: This is my_function's docstring.
如果没有 @functools.wraps(func)
,my_function.__name__
会是 wrapper
,my_function.__doc__
会是 wrapper
函数的文档字符串。
装饰器是 Python 中一个非常强大且常用的高级特性,理解它对于编写简洁、可维护和可扩展的代码至关重要。
你对装饰器还有什么具体的问题吗?