这可以说是 Python 装饰器进阶的一个关键点,理解它需要一层一层地剥开来看。
核心思想
要理解为什么需要带参数的装饰器。
-
普通装饰器:它接收一个函数作为参数,并返回一个新的函数,它的作用是“包装”或“增强”原函数,一个打印日志的装饰器,它不需要任何外部配置。
@my_decorator def my_func(): # ... -
带参数的装饰器:我们想让装饰器本身可以接收参数,以便在装饰时进行更灵活的配置,我们想控制日志装饰器是打印
INFO级别还是DEBUG级别的日志。# 我们想把 "DEBUG" 这个参数传递给装饰器 @my_decorator(level="DEBUG") def my_func(): # ...
看起来语法上 @my_decorator(level="DEBUG") 像是在调用一个函数,没错!这正是理解它的关键。
分步理解:从内到外
一个带参数的装饰器,实际上是一个返回装饰器函数的函数,我们来分解这个过程,假设我们有这样一个需求:创建一个可以指定前缀的日志装饰器。
第 1 步:创建一个普通装饰器(不带参数)
我们先写一个最简单的日志装饰器,它只负责在函数执行前后打印日志。
import time
def simple_log_decorator(func):
"""一个简单的日志装饰器,不带参数"""
def wrapper(*args, **kwargs):
print(f"--- 开始执行函数: {func.__name__} ---")
start_time = time.time()
result = func(*args, **kwargs) # 执行原函数
end_time = time.time()
print(f"--- 函数 {func.__name__} 执行完毕,耗时: {end_time - start_time:.4f} 秒 ---")
return result
return wrapper
使用方式:
@simple_log_decorator
def say_hello(name):
time.sleep(0.5)
print(f"你好, {name}!")
say_hello("Alice")
输出:
--- 开始执行函数: say_hello ---
你好, Alice!
--- 函数 say_hello 执行完毕,耗时: 0.5001 秒 ---
第 2 步:引入参数的需求
我们希望在函数执行前打印的日志可以带上一个自定义的前缀,[INFO] 或 [WARNING]。
-
错误尝试:我们不能直接在
simple_log_decorator里加一个prefix参数,因为 语法后面必须跟一个可调用对象(函数),Python 会自动将被装饰的函数 (say_hello) 作为参数传给它。# 这么写是错误的! @simple_log_decorator(prefix="[INFO]") # Python 会尝试执行 simple_log_decorator("[INFO]"),然后把返回值再作为装饰器 def say_hello(name): # ...
第 3 步:创建“工厂”函数
为了满足 @my_decorator(prefix="...") 这样的语法,我们需要一个工厂函数,这个函数本身接收我们想传入的参数(如 prefix),然后返回一个真正的装饰器。
我们把这个工厂函数叫做 log_decorator_with_prefix。
def log_decorator_with_prefix(prefix):
"""
这是一个“工厂函数”,它接收 prefix 参数,
并返回一个真正的装饰器。
"""
# 1. 内部定义真正的装饰器
def actual_decorator(func):
"""
这个 actual_decorator 和我们第一步的 simple_log_decorator 长得一样,
但它现在可以访问到外层的 prefix 变量。
"""
def wrapper(*args, **kwargs):
print(f"{prefix} --- 开始执行函数: {func.__name__} ---")
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{prefix} --- 函数 {func.__name__} 执行完毕,耗时: {end_time - start_time:.4f} 秒 ---")
return result
return wrapper
# 2. 工厂函数返回这个真正的装饰器
return actual_decorator
第 4 步:使用带参数的装饰器
现在我们可以这样使用了:
@log_decorator_with_prefix(prefix="[INFO]")
def say_info(name):
time.sleep(0.3)
print(f"这是信息: {name}")
@log_decorator_with_prefix(prefix="[WARNING]")
def say_warning(name):
time.sleep(0.2)
print(f"这是警告: {name}")
print("--- 调用 say_info ---")
say_info("Bob")
print("\n--- 调用 say_warning ---")
say_warning("Charlie")
输出结果:
--- 调用 say_info ---
[INFO] --- 开始执行函数: say_info ---
这是信息: Bob
[INFO] --- 函数 say_info 执行完毕,耗时: 0.3001 秒 ---
--- 调用 say_warning ---
[WARNING] --- 开始执行函数: say_warning ---
这是警告: Charlie
[WARNING] --- 函数 say_warning 执行完毕,耗时: 0.2001 秒 ---
可以看到,通过给装饰器传入不同的 prefix,我们成功地定制了日志输出。
当 Python 解释器遇到 @log_decorator_with_prefix(prefix="[INFO]") 时,它会做以下事情:
- 首先调用工厂函数:执行
log_decorator_with_prefix(prefix="[INFO]")。 - 获取返回值:这个调用会返回内部的
actual_decorator函数。 - 应用装饰器:然后将返回的
actual_decorator应用到下面的say_info函数上,这相当于执行了say_info = actual_decorator(say_info)。
@my_decorator(arg) 的完整展开形式就是 my_func = my_decorator(arg)(my_func)。
完整示例:带参数的类装饰器
装饰器不仅可以是函数,也可以是类,带参数的类装饰器同样遵循“工厂”模式。
类装饰器基本原理
一个类要作为装饰器,必须实现 __call__ 方法,当类的实例被调用时(就像函数一样),__call__ 方法就会被执行。
class SimpleClassDecorator:
def __init__(self, func):
print(f"装饰器初始化,正在包装 {func.__name__}")
self.func = func
def __call__(self, *args, **kwargs):
print(f"类装饰器:在 {self.func.__name__} 执行前")
result = self.func(*args, **kwargs)
print(f"类装饰器:在 {self.func.__name__} 执行后")
return result
@SimpleClassDecorator
def my_class_func():
print("我是被类装饰的函数")
my_class_func()
输出:
装饰器初始化,正在包装 my_class_func
类装饰器:在 my_class_func 执行前
我是被类装饰的函数
类装饰器:在 my_class_func 执行后
带参数的类装饰器
我们想让这个类装饰器也能接收参数,我们只需要再加一层“外壳”类作为工厂即可。
class ConfigurableClassDecorator:
def __init__(self, prefix=""):
"""
工厂类的 __init__ 方法接收装饰器的参数。
这个实例不会被直接调用,而是作为最终的装饰器。
"""
self.prefix = prefix
print(f"工厂类已创建,prefix 为: '{self.prefix}'")
def __call__(self, func):
"""
这个 __call__ 方法才是真正的装饰器逻辑。
它接收被装饰的函数 func。
"""
print(f"正在用配置 '{self.prefix}' 装饰函数 {func.__name__}")
def wrapper(*args, **kwargs):
print(f"{self.prefix} -> 开始执行 {func.__name__}")
result = func(*args, **kwargs)
print(f"{self.prefix} -> {func.__name__} 执行完毕")
return result
return wrapper
# 使用方式
@ConfigurableClassDecorator(prefix="[CLASS-INFO]")
def my_configurable_func():
print("执行核心逻辑...")
print("\n--- 开始调用函数 ---")
my_configurable_func()
输出结果:
工厂类已创建,prefix 为: '[CLASS-INFO]'
正在用配置 '[CLASS-INFO]' 装饰函数 my_configurable_func
--- 开始调用函数 ---
[CLASS-INFO] -> 开始执行 my_configurable_func
执行核心逻辑...
[CLASS-INFO] -> my_configurable_func 执行完毕
| 类型 | 结构 | 核心思想 | 使用示例 |
|---|---|---|---|
| 普通装饰器 | def decorator(func): ... |
接收一个函数,返回一个新函数。 | @my_decorator |
| 带参数的函数装饰器 | def decorator_factory(args): <br> def actual_decorator(func): ... <br> return actual_decorator <br> return actual_decorator |
一个工厂函数,接收参数并返回一个真正的装饰器。 | @my_decorator_factory(arg) |
| 带参数的类装饰器 | class DecoratorFactory: <br> def __init__(self, args): ... <br> def __call__(self, func): ... |
一个工厂类,__init__接收参数,__call__作为真正的装饰器逻辑。 |
@DecoratorFactory(arg) |
掌握了“工厂函数/类返回真正的装饰器”这一核心思想,你就能自如地使用和理解带参数的装饰器了。
