不带参数的装饰器
我们快速回顾一下标准装饰器的工作原理,因为带参数的装饰器是在此基础上构建的。

(图片来源网络,侵删)
一个标准的装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数。
示例:
def my_decorator(func):
"""一个简单的装饰器,用于在函数执行前后打印信息"""
def wrapper():
print("函数执行前...")
func() # 调用原始函数
print("函数执行后...")
return wrapper
@my_decorator
def say_hello():
print("Hello, World!")
# 调用被装饰的函数
say_hello()
输出:
函数执行前...
Hello, World!
函数执行后...
过程分解:

(图片来源网络,侵删)
- 当 Python 解释器看到
@my_decorator时,它实际上执行了say_hello = my_decorator(say_hello)。 - 这意味着
say_hello这个名字现在指向了my_decorator函数返回的wrapper内部函数。 - 当我们调用
say_hello()时,实际上是在调用wrapper()。
带参数的装饰器
我们想要让装饰器本身可以接收参数,我们想自定义打印的消息。
问题: 如果我们直接修改 my_decorator,让它接收一个参数 message,会发生什么?
# 错误的尝试
def my_decorator_with_arg(message):
def wrapper(func):
def inner_wrapper():
print(f"函数执行前... ({message})")
func()
print(f"函数执行后... ({message})")
return inner_wrapper
return wrapper
@my_decorator_with_arg("这是自定义消息")
def say_goodbye():
print("Goodbye, World!")
say_goodbye()
为什么这样写?
你会发现,带参数的装饰器需要三层嵌套:

(图片来源网络,侵删)
-
最外层函数 (
my_decorator_with_arg):- 它接收你传递给装饰器的参数(
"这是自定义消息")。 - 它的返回值是一个装饰器(也就是
wrapper函数)。
- 它接收你传递给装饰器的参数(
-
中间层函数 (
wrapper):- 它接收被装饰的原始函数(
say_goodbye)。 - 它的返回值是一个包装函数(也就是
inner_wrapper)。
- 它接收被装饰的原始函数(
-
最内层函数 (
inner_wrapper):它真正执行原始函数的逻辑,并可以访问到外层函数的参数(通过闭包)。
执行流程分解:
当 Python 解释器看到 @my_decorator_with_arg("这是自定义消息") 时,它会按以下顺序执行:
- 首先调用
my_decorator_with_arg("这是自定义消息")。 - 这个调用会返回
wrapper函数。 - Python 会将 后面的表达式(即返回的
wrapper函数)应用到say_goodbye函数上,这相当于执行say_goodbye = wrapper(say_goodbye)。 say_goodbye指向的是inner_wrapper函数。
上面的代码输出:
函数执行前... (这是自定义消息)
Goodbye, World!
函数执行后... (这是自定义消息)
实战案例:带参数的计时装饰器
让我们来看一个更实用的例子:一个可以指定重复次数的计时装饰器。
import time
import functools
def repeat(times):
"""
一个带参数的装饰器,用于重复执行函数并计时。
:param times: 重复执行的次数
"""
def decorator(func):
@functools.wraps(func) # 好习惯,保留原始函数的元信息(如 __name__, __doc__)
def wrapper(*args, **kwargs):
total_time = 0
print(f"--- 开始执行函数 '{func.__name__}' {times} 次 ---")
for i in range(times):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
total_time += (end_time - start_time)
print(f" 第 {i+1} 次执行耗时: {end_time - start_time:.6f} 秒")
average_time = total_time / times
print(f"--- 总耗时: {total_time:.6f} 秒, 平均耗时: {average_time:.6f} 秒 ---")
return result
return wrapper
return decorator
# 使用装饰器
@repeat(times=3)
def process_data(data):
"""模拟一个数据处理函数"""
print(f"正在处理数据: {data}")
time.sleep(0.5) # 模拟耗时操作
# 调用被装饰的函数
process_data("sample_data.txt")
输出:
--- 开始执行函数 'process_data' 3 次 ---
正在处理数据: sample_data.txt
第 1 次执行耗时: 0.500123 秒
正在处理数据: sample_data.txt
第 2 次执行耗时: 0.500456 秒
正在处理数据: sample_data.txt
第 3 次执行耗时: 0.500789 秒
--- 总耗时: 1.501368 秒, 平均耗时: 0.500456 秒 ---
代码解析:
repeat(times)是最外层函数,接收参数times。decorator(func)是中间层函数,接收被装饰的函数process_data。wrapper(*args, **kwargs)是最内层函数,它使用*args和**kwargs来接收process_data的任意参数,使其成为一个通用的装饰器。@functools.wraps(func)是一个非常好的实践,它将原始函数的__name__,__doc__等属性复制到wrapper函数上,这样在调试或使用help()时,信息会更准确。
如何记忆和理解
你可以把带参数的装饰器想象成一个“装饰器工厂”。
-
不带参数的装饰器:你直接拿到一个装饰器,把它用在函数上。
@my_decorator def my_func(): ... # 等价于 my_func = my_decorator(my_func)
-
带参数的装饰器:你先调用一个“工厂函数”,这个工厂根据你给的参数,为你量身定做一个合适的装饰器,然后再把这个定制的装饰器用在函数上。
@my_decorator_factory(arg1, arg2) def my_func(): ... # 等价于 my_func = my_decorator_factory(arg1, arg2)(my_func) # 这分两步: # 1. decorator = my_decorator_factory(arg1, arg2) # 2. my_func = decorator(my_func)
记住这个三层嵌套的结构,你就能轻松理解并写出任何复杂的带参数装饰器了:
- 第一层:接收装饰器参数。
- 第二层:接收原始函数。
- 第三层:执行核心逻辑,并可以访问到第一层的参数和第二层的函数。
