装饰器是一个非常有用而又常被误解的功能,可以让我们在不修改函数或类的源代码情况下给它们提供扩展功能。本文将通过具体示例带你深入理解 Python
装饰器的用法。
装饰器基础
装饰器本质上是一个函数,它可以让其他函数在不需要做任何代码变动的前提下添加额外功能。装饰器的语法如下
@decorator
def func():
pass
这里的 @decorator
就表示使用 decorator
这个装饰器来装饰后面的函数。
我们来看一个具体的例子
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Call {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log
def add(x, y):
return x + y
print(add(2, 3))
执行上述代码,输出结果为
Call add
5
这里我们定义了一个名为 log
装饰器,它会打印函数名称然后再调用原函数。通过 @log
就可以来装饰 add
函数,使其获得打印日志的功能。
带参数的装饰器
装饰器本身也可以带参数,需要多一层封装
from functools import wraps
def repeat(num):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(num=3)
def greet(name):
print(f"Hello {name}")
greet("xgx")
执行上述脚本,输出结果为
Hello xgx
Hello xgx
Hello xgx
这种带参数的装饰器在一些特殊场景下非常有用,如需要自定义执行的次数。
装饰类
装饰器不仅可以装饰函数,还可以装饰类,看下面的示例
from functools import wraps
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__!r}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello(name):
print(f"Hello {name}")
say_hello("xgx")
say_hello("Alice")
执行上述脚本,输出结果为
Call 1 of 'say_hello'
Hello xgx
Call 2 of 'say_hello'
Hello Alice
这里我们定义了一个 CountCalls
类,实现了 __init__()
和 __call__()
方法。使用 @CountCalls
装饰 say_hello
函数时,会先创建 CountCalls
实例对象,并将 say_hello
函数存入实例的 func
属性。在调用 say_hello
时,实际上调用的是 CountCalls
实例对象,它会更新调用次数,打印信息,最后再调用原始的 say_hello
函数。这样就实现了一个统计调用次数的装饰器。
类装饰器的好处是可以存储状态,方便扩展额外的功能。
多个装饰器
多个装饰器可以层层嵌套,执行顺序由里到外。
@decorator1
@decorator2
def func():
pass
例如
@repeat(num=3)
@log
def greet(name):
print(f"Hello {name}")
greet
函数先由 @log
装饰,然后由 @repeat
装饰。
所以装饰器的顺序会影响函数的行为。
总结
装饰器是一个非常强大和有用的功能,可以让我们在不修改源代码的情况下动态扩展函数和类的功能,是每个 Python
程序员都应该掌握的重要知识点。