有关最新版本,请在Pythongasm
上阅读本文介绍
想象一下,您已经生产出非常好的蛋糕。现在,您的客户突然希望您用樱桃装饰蛋糕。公平的需求,但是如果我是你,我不想拆卸我的蛋糕机来添加将樱桃放在顶部的功能。
这不仅是因为我懒得重新审视机器的内部(至少这不是唯一的原因),而且因为这样做会违反许多工程原则:
- 耦合 - 我不应该做使系统紧密耦合的事情
- 单一责任 - 蛋糕机有单一的责任;它应该继续是其唯一责任
- 灵活性 - 如果客户会要求浆果而不是樱桃,我将不得不做到这一点。
,如果我们只制造一台机器,我们就可以解决所有这些问题,这只会用樱桃装饰蛋糕机制作的蛋糕。我们称其为装饰机。
它不仅可以解决上述问题,而且现在您还可以将其用于装饰樱桃的其他产品。
Decorator是Python中的设计模式,可减少代码重复并增加脱钩。
用法
我一直在研究一个具有大量计算的密集机器学习功能的系统:
@cpuseconds
def train():
"""
function to train a model
"""
…
@cpuseconds
def predict(data, model):
"""
function to predict using a model
"""
…
我们想查看每个客户消耗多少CPU秒(机器上的代码执行时间)以及什么功能。
正如我们在蛋糕机器中已经看到的那样,编辑这些宁静的功能不是一个好主意。因此,让我们做一个可以做到这一点的装饰器。
import time
def cpuseconds(func):
"""
decorator method
to calculate time elapsed
in execution of func
"""
def wrapper(*args, **kwargs):
start_time = time.time()
response = func(*args, **kwargs)
time_elapsed = time.time()-start_time
with open('resource-util.txt','a+') as f:
f.write(f"{func.__name__},{time_elapsed}\n")
return response
return wrapper
现在,我们可以使用此装饰函数来装饰现有功能:
@cpuseconds
def train():
…
@cpuseconds
def predict(data, model):
…
运行这些功能时,您将拥有一个文件,其中包含有关每个功能调用时间利用率的记录。
请注意,您已经更改了功能的行为,而无需修改原始功能中的任何内容。计划和简单。
但是,这个故事还有更多。在进行进一步之前,让我们使用dunder方法__doc__
打印原始函数的文档字符串。
>>> train.__doc__
decorator method
to calculate time elapsed
in execution of func
糟糕。这不是我们期望的。要了解为什么,我们需要深入研究,看看如何在Python实施装饰师。
这个怎么做的
装饰师在一流功能的概念上发挥作用。这只是意味着Python中的功能像其他任何对象一样对待。
您可以将功能传递为其他功能的参数,例如通过字符串,整数和任何其他Python对象。
在上面的示例中,通过使用cpuseconds
Decorator,我们实际上是在函数cpuseconds
中传递“火车”功能,该功能实际上返回了其他功能,该功能实际上返回了另一个功能。这几乎是相同的,但顶部有樱桃。
因此,这意味着火车顶部的@cpuseconds转换为:
train = cpuseconds(train)
我们将火车变量指向新功能。请注意,新功能的输出是一个函数,尚未执行。
这意味着我们实际上并没有调用我们的原始火车功能,因为变量列车已被重新分配到其他东西 - 巧合的是具有原始火车功能的某些特征(例如,返回相同的响应),但没有train
功能本身。
因此,看到train.__doc__
甚至train.__name__
的变化也就不足为奇了。
@functools.wraps
幸运的是,Python有一个内置的装饰师@functools.wraps
。您可以使用此装饰器来装饰包装器功能。这将将原始功能的某些属性(例如__doc__
,__name__
等)复制到包装器功能,从而保留信息。
from functools import wraps
def cpuseconds(func):
@wraps
def wrapper(*args, **kwargs):
…
return wrapper
现在让我们再试一次:
train.__doc__
function to train a model
@wraps在锁定多个装饰器时也很方便。
结论
这是关于装饰商及其现实世界用例的工作的深入文章。我们在本文中没有触摸其他主题,例如,基于类的装饰器,装饰器功能中的争论等等
。