1. 简介
Python装饰器(Decorator)是一种设计模式,在Python中被广泛应用。装饰器主要作用是把一个函数或者类进行额外的功能添加或者修改而不需要修改这个函数或类的主体结构。
由于Python支持函数也可以被当做参数传递,从而可以在装饰器函数外面包一层函数包装装饰器,并在这个函数中返回内层函数,这样就实现了一个装饰器。最近,Python 2.X 版本更新到了 Python 3.X 版本,由于 Python 3.X 版本更新后的 print 是函数,而在 Python 2.X 版本中 print 是关键字,因此可能在不同版本的 Python 中会出现不同的错误。
def function_name(function):
def decorator():
print("函数开始执行")
function()
print("函数执行结束")
return decorator
@function_name
def show():
print("我是函数show")
1.1 装饰器的使用方法
装饰器语法示例:@decorator_function_name
函数定义时,在函数名前面加上 @decorator_function_name
,这样就可以把后面的函数当做参数传递给decorator_function_name
函数,decorator_function_name
函数返回的函数就是新的函数。
1.2 装饰器的优点
使用装饰器可以:
代码重复最小化,例如,在多个函数中需要添加相同的功能,使用装饰器函数实现这个功能,从而摆脱代码重复带来的复杂性。
在不改变原函数或类的情况下,修改或添加新的函数或类的功能,这种功能是非常有用的。
2. 装饰器案例
下面是一个使用装饰器的例子,在这个例子中打印函数开始执行和函数结束执行的时间戳:
import time
def time_it(fn):
def wrapper(*args, **kwargs):
start = time.monotonic()
result = fn(*args, **kwargs)
end = time.monotonic()
print(f"Elapsed time (in seconds): {end - start:.6f}")
return result
return wrapper
@time_it
def do_something():
for i in range(1000000):
pass
print("Done doing something")
do_something()
在上面的代码中,我们定义了一个time_it
装饰器函数,这个装饰器函数返回一个wrapper
函数,在这个wrapper
函数中添加了打印函数执行时间戳的功能。最后,在do_something
函数上面使用了time_it
装饰器。
3. 多个装饰器的使用
使用多个装饰器的语法格式如前面讲述的单个装饰器语法格式,只需要在函数名前面添加上每一个要使用的装饰器函数即可。在 Python 里面,这种语法也是 Python 装饰器语法最基础的形式。
def decorator1(fn):
def wrapper():
print("decorator1 before function")
fn()
print("decorator1 after function")
return wrapper
def decorator2(fn):
def wrapper():
print("decorator2 before function")
fn()
print("decorator2 after function")
return wrapper
@decorator1
@decorator2
def some_function():
print("Function is running")
some_function()
在上述代码中,函数some_function被两个装饰器进行修饰。
4. 装饰器带参数
装饰器可以带参数,修改示例time_it
函数,让它可以接受一个参数repeat_count
,以便指定函数应该重复执行多少次,并遇到错误时重试次数:
import time
def time_it(repeat_count=1):
def decorator(fn):
def wrapper(*args, **kwargs):
total_elapsed = 0
for i in range(repeat_count):
start = time.monotonic()
try:
result = fn(*args, **kwargs)
except Exception as e:
print(f"Exception: {e}")
else:
end = time.monotonic()
total_elapsed += (end - start)
print(f"Elapsed time (in seconds): {end - start:.6f}")
print(f"Average time (in seconds): {total_elapsed / repeat_count:.6f}")
return result
return wrapper
return decorator
@time_it(repeat_count=3)
def do_something():
for i in range(1000000):
pass
print("Done doing something")
do_something()
在上面的代码中,我们引入了一个新的函数decorator
,这个函数可以接受repeat_count
参数,这样用户就可以在定义do_something
函数时指定repeat_count
的值。
5. 练习题
5.1 编写一个注册器,记录注册函数的名字及日志
要求实现一个注册器(register),它可以在注册的函数被调用时,记录日志及其所包含的参数。它应该可以可在用装饰器语法附加到函数上,可以在任何函数上添加装饰器。而且,该注册器应该可以使用带有可选参数的装饰器,并应该使用该装饰器时允许传递该参数。
def register(fn):
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
print(f"Log: Call to {fn.__name__} ({args}, {kwargs}) returned {result}")
return result
return wrapper
# Registerer with required arguments
@register
def add(x, y):
return x + y
# Registerer with optional arguments
@register(precision=4)
def divide(x, y):
return x / y
# Time the decorated function
@register
def time_it(fn):
import time
def wrapper(*args, **kwargs):
start_time = time.monotonic()
result = fn(*args, **kwargs)
end_time = time.monotonic()
print(f"Timing: {fn.__name__} took {end_time - start_time:.6f} seconds")
return result
return wrapper
add(2, 3)
divide(22, 7)
time_it(divide)(22, 7)
运行上面的代码,对一个加法函数,一个除法函数,以及一个使用装饰器time_it
附加在除法函数上的函数进行注册并运行。在注册完成后,会输出函数的日志、参数和函数运行结果。
5.2 自定义装饰器
请实现一个自定义的装饰器,将类中的函数进行计数并打印出执行次数。
class Counter():
def __init__(self, fn):
self.fn = fn
self.counter = 0
def __call__(self, *args):
self.counter += 1
result = self.fn(*args)
print(f"Function '{self.fn.__name__}' has been called {self.counter} times.")
return result
class Test():
@Counter
def run(self, n):
res = 0
for i in range(n+1):
res += i
return res
t = Test()
for i in range(10):
t.run(i)
运行上面的代码,将统计类Test的函数run
被执行的次数并打印出执行次数。