Python装饰器利用函数为一等公民和闭包特性,通过@语法为函数添加功能而不修改其代码。如log_calls装饰器可记录函数调用日志,核心是外部函数返回嵌套的wrapper函数,wrapper保留对原函数的引用并扩展行为。functools.wraps确保被装饰函数的元信息不变。带参数的装饰器需多一层函数嵌套,形成“装饰器工厂”,如timer(unit)返回真正的装饰器。类也可作为装饰器,通过实现__call__方法,在实例中保存状态,适用于需维护调用次数或共享资源的场景,如CallCounter统计函数调用次数。

Python装饰器,说白了,就是一种特殊的函数,它的主要工作是去“包裹”或者说“包装”另一个函数,给这个被包装的函数增加额外的功能,但又不需要我们去直接修改被包装函数的源代码。这听起来有点像给一个礼物盒外面再套一层包装纸,里面的礼物(原函数)还是那个礼物,但外面的包装纸(装饰器)给它增添了新的“仪式感”或者说“功能”。它的核心原理,其实就是利用了Python中函数是“一等公民”的特性,以及闭包(closure)的概念,通过
@
要实现一个装饰器,我们通常会定义一个外部函数,这个外部函数接收一个函数作为参数(也就是我们要装饰的那个函数)。在外部函数内部,我们再定义一个嵌套函数(通常命名为
wrapper
wrapper
wrapper
举个最常见的例子,我们想给一个函数加上日志功能,记录它被调用的时间和参数:
import time
import functools
def log_calls(func):
"""
一个简单的日志装饰器,记录函数调用。
"""
@functools.wraps(func) # 这一行很重要,它保留了原函数的元信息
def wrapper(*args, **kwargs):
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 调用函数: {func.__name__},参数: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 函数 {func.__name__} 执行完毕,返回: {result}")
return result
return wrapper
@log_calls
def add(a, b):
"""计算两个数的和"""
time.sleep(0.1) # 模拟耗时操作
return a + b
@log_calls
def greet(name, greeting="Hello"):
"""向指定名字的人打招呼"""
return f"{greeting}, {name}!"
# 调用被装饰的函数
print(f"结果: {add(10, 20)}")
print(f"结果: {greet('Alice', greeting='Hi')}")这里,
log_calls
add
@log_calls
add = log_calls(add)
add
add
log_calls
wrapper
add(10, 20)
wrapper(10, 20)
wrapper
add
functools.wraps
wrapper
立即学习“Python免费学习笔记(深入)”;
在我看来,要真正理解装饰器,就得先搞明白Python里“函数是第一类对象(First-Class Citizen)”这个概念,以及“闭包(Closure)”是什么。这不光是装饰器的基石,也是Python很多高级特性的核心。
函数作为一等公民,意味着函数在Python里和整数、字符串这些数据类型没什么两样。你可以:
my_func = add
map(str, [1, 2, 3])
wrapper
而闭包,则是在一个函数内部定义了另一个函数,并且内部函数引用了外部函数的局部变量,当外部函数执行完毕并返回内部函数时,即使外部函数的执行环境已经销毁,内部函数仍然能够“记住”并访问外部函数的那些局部变量。
在我们的
log_calls
log_calls
func
add
greet
log_calls
wrapper
func
log_calls
wrapper
wrapper
func
log_calls
wrapper
这种机制非常强大,它允许我们在不修改原函数代码的前提下,对其行为进行扩展。这在很多场景下都极其有用,比如权限验证、缓存、性能监控、事务管理等等,都是典型的“横切关注点”,用装饰器来处理简直是天作之合。
有时候,我们希望装饰器本身也能接受一些配置参数,比如一个日志装饰器,我们可能想指定日志级别,或者一个权限装饰器,我们想指定需要的角色。这时,我们的装饰器就需要变成一个“装饰器工厂”,也就是说,一个函数,它接收参数,然后返回一个真正的装饰器。
这个模式会多一层嵌套,看起来可能会有点绕,但理解了前面闭包的概念,这也就水到渠成了。
import time
import functools
def timer(unit="seconds"):
"""
一个带参数的计时装饰器,可以指定时间单位。
unit: 'seconds', 'milliseconds', 'microseconds'
"""
def decorator(func): # 这才是真正的装饰器
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
duration = end_time - start_time
if unit == "milliseconds":
duration *= 1000
unit_str = "ms"
elif unit == "microseconds":
duration *= 1_000_000
unit_str = "μs"
else:
unit_str = "s"
print(f"函数 {func.__name__} 执行耗时: {duration:.4f} {unit_str}")
return result
return wrapper
return decorator
@timer(unit="milliseconds") # 这里传递了参数
def complex_calculation(n):
"""模拟一个复杂的计算"""
total = 0
for i in range(n):
total += i * i
time.sleep(0.05) # 额外模拟一点IO耗时
return total
@timer() # 不传参数时,使用默认单位
def simple_task():
"""一个简单的任务"""
time.sleep(0.02)
return "Task Done"
print(f"计算结果: {complex_calculation(100000)}")
print(f"任务状态: {simple_task()}")这里,
timer
unit
decorator
decorator
wrapper
@timer(unit="milliseconds")
timer("milliseconds")decorator
decorator
complex_calculation
complex_calculation = decorator(complex_calculation)
unit
decorator
wrapper
wrapper
除了函数装饰器,Python还允许我们使用类来作为装饰器。类装饰器在某些场景下,比如需要维护状态、或者需要更复杂的初始化逻辑时,会显得更加直观和强大。
一个类要作为装饰器,最核心的一点是它需要实现
__call__
@ClassName
ClassName
import time
import functools
class CallCounter:
"""
一个类装饰器,用于统计函数被调用的次数。
"""
def __init__(self, func):
# 初始化时,接收被装饰的函数
functools.update_wrapper(self, func) # 同样保留原函数元信息
self.func = func
self.count = 0 # 维护调用次数的状态
def __call__(self, *args, **kwargs):
# 当被装饰的函数被调用时,实际上是调用了__call__方法
self.count += 1
print(f"函数 {self.func.__name__} 已被调用 {self.count} 次。")
return self.func(*args, **kwargs)
@CallCounter
def calculate_sum(a, b):
"""计算和"""
return a + b
@CallCounter
def say_hello(name):
"""打招呼"""
return f"Hello, {name}!"
# 调用被装饰的函数
print(calculate_sum(1, 2))
print(calculate_sum(3, 4))
print(say_hello("World"))
print(calculate_sum(5, 6))这里,
CallCounter
@CallCounter
calculate_sum
calculate_sum = CallCounter(calculate_sum)
calculate_sum
CallCounter
calculate_sum(1, 2)
__call__
类装饰器特别适合需要内部状态或者需要在多个被装饰函数之间共享某些配置或资源的场景。比如,一个数据库连接池的装饰器,或者一个复杂的缓存机制,用类来实现可能会让代码结构更清晰,状态管理也更集中。当然,这并不是说函数装饰器就不能实现有状态的,只是类提供了一种更面向对象的封装方式。选择哪种方式,很多时候取决于具体的需求和个人偏好,但了解它们的原理和适用场景,总能帮助我们做出更明智的决策。
以上就是python如何实现一个装饰器_python装饰器原理与实现方法详解的详细内容,更多请关注php中文网其它相关文章!
python怎么学习?python怎么入门?python在哪学?python怎么学才快?不用担心,这里为大家提供了python速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号