函数装璜器
装璜器(Decorators)在 Python 中,次要作用是批改函数的性能,而且批改前提是不变动原函数代码,装璜器会返回一个函数对象,所以有的中央会把装璜器叫做 “函数的函数”。
还存在一种设计模式叫做 “装璜器模式”,这个后续的课程会有所波及。
装璜器调用的时候,应用 @
,它是 Python 提供的一种编程语法糖,应用了之后会让你的代码看起来更加 Pythonic
。
装璜器根本应用
在学习装璜器的时候,最常见的一个案例,就是统计某个函数的运行工夫,接下来就为你分享一下。
计算函数运行工夫:
import timedef fun(): i = 0 while i < 1000: i += 1def fun1(): i = 0 while i < 10000: i += 1s_time = time.perf_counter()fun()e_time = time.perf_counter()print(f"函数{fun.__name__}运行工夫是:{e_time-s_time}")
如果你心愿给每个函授都加上调用工夫,那工作量是微小的,你须要反复的批改函数外部代码,或者批改函数调用地位的代码。在这种需要下,装璜器语法呈现了。
先看一下第一种批改办法,这种办法没有减少装璜器,然而编写了一个通用的函数,利用 Python 中函数能够作为参数这一个性,实现了代码的可复用性。
import timedef fun(): i = 0 while i < 1000: i += 1def fun1(): i = 0 while i < 10000: i += 1def go(fun): s_time = time.perf_counter() fun() e_time = time.perf_counter() print(f"函数{fun.__name__}运行工夫是:{e_time-s_time}")if __name__ == "__main__": go(fun1)
接下来这种技巧扩大到 Python 中的装璜器语法,具体批改如下:
import timedef go(func): # 这里的 wrapper 函数名能够为任意名称 def wrapper(): s_time = time.perf_counter() func() e_time = time.perf_counter() print(f"函数{func.__name__}运行工夫是:{e_time-s_time}") return wrapper@godef func(): i = 0 while i < 1000: i += 1@godef func1(): i = 0 while i < 10000: i += 1if __name__ == '__main__': func()
在上述代码中,留神看 go
函数局部,它的参数 func
是一个函数,返回值是一个外部函数,执行代码之后相当于给原函数注入了计算工夫的代码。在代码调用局部,你没有做任何批改,函数 func
就具备了更多的性能(计算运行工夫的性能)。
装璜器函数胜利拓展了原函数的性能,又不须要批改原函数代码,这个案例学会之后,你就曾经初步理解了装璜器。
对带参数的函数进行装璜
间接看代码,理解如何对带参数的函数进行装璜:
import timedef go(func): def wrapper(x, y): s_time = time.perf_counter() func(x, y) e_time = time.perf_counter() print(f"函数{func.__name__}运行工夫是:{e_time-s_time}") return wrapper@godef func(x, y): i = 0 while i < 1000: i += 1 print(f"x={x},y={y}")if __name__ == '__main__': func(33, 55)
如果你看着晕乎了,我给你标记一下参数的重点传递过程。
还有一种状况是装璜器自身带有参数,例如下述代码:
def log(text): def decorator(func): def wrapper(x): print('%s %s():' % (text, func.__name__)) func(x) return wrapper return decorator@log('执行')def my_fun(x): print(f"我是 my_fun 函数,我的参数 {x}")my_fun(123)
上述代码在编写装璜器函数的时候,在装璜器函数外层又嵌套了一层函数,最终代码的运行程序如下所示:
my_fun = log('执行')(my_fun)
此时如果咱们总结一下,就能失去论断了:应用带有参数的装璜器,是在装璜器里面又包裹了一个函数,应用该函数接管参数,并且返回一个装璜器函数。
还有一点要留神的是装璜器只能接管一个参数,而且必须是函数类型。

多个装璜器
先临摹一下下述代码,再进行学习与钻研。
import timedef go(func): def wrapper(x, y): s_time = time.perf_counter() func(x, y) e_time = time.perf_counter() print(f"函数{func.__name__}运行工夫是:{e_time-s_time}") return wrapperdef gogo(func): def wrapper(x, y): print("我是第二个装璜器") return wrapper@go@gogodef func(x, y): i = 0 while i < 1000: i += 1 print(f"x={x},y={y}")if __name__ == '__main__': func(33, 55)
代码运行之后,输入后果为:
我是第二个装璜器函数wrapper运行工夫是:0.0034401339999999975
虽说多个装璜器应用起来非常简单,然而问题也呈现了,print(f"x={x},y={y}")
这段代码运行后果失落了,这里就波及多个装璜器执行程序问题了。
先解释一下装璜器的装璜程序。
import timedef d1(func): def wrapper1(): print("装璜器1开始装璜") func() print("装璜器1完结装璜") return wrapper1def d2(func): def wrapper2(): print("装璜器2开始装璜") func() print("装璜器2完结装璜") return wrapper2@d1@d2def func(): print("被装璜的函数")if __name__ == '__main__': func()
上述代码运行的后果为:
装璜器1开始装璜装璜器2开始装璜被装璜的函数装璜器2完结装璜装璜器1完结装璜
能够看到十分对称的输入,同时证实被装璜的函数在最内层,转换成函数调用的代码如下:
d1(d2(func))
你在这部分须要留神的是,装璜器的外函数和内函数之间的语句,是没有装璜到指标函数上的,而是在装载装璜器时的附加操作。
在对函数进行装璜的时候,外函数与内函数之间的代码会被运行。
测试成果如下:
import timedef d1(func): print("我是 d1 内外函数之间的代码") def wrapper1(): print("装璜器1开始装璜") func() print("装璜器1完结装璜") return wrapper1def d2(func): print("我是 d2 内外函数之间的代码") def wrapper2(): print("装璜器2开始装璜") func() print("装璜器2完结装璜") return wrapper2@d1@d2def func(): print("被装璜的函数")
运行之后,你就能发现输入后果如下:
我是 d2 内外函数之间的代码我是 d1 内外函数之间的代码
d2
函数早于 d1
函数运行。
接下来在回顾一下装璜器的概念:
被装璜的函数的名字会被当作参数传递给装璜函数。
装璜函数执行它本人外部的代码后,会将它的返回值赋值给被装璜的函数。
这样看上文中的代码运行过程是这样的,d1(d2(func))
执行 d2(func)
之后,原来的 func
这个函数名会指向 wrapper2
函数,执行 d1(wrapper2)
函数之后,wrapper2
这个函数名又会指向 wrapper1
。因而最初的 func
被调用的时候,相当于代码曾经切换成如下内容了。
# 第一步def wrapper2(): print("装璜器2开始装璜") print("被装璜的函数") print("装璜器2完结装璜")# 第二步print("装璜器1开始装璜")wrapper2()print("装璜器1完结装璜")# 第三步def wrapper1(): print("装璜器1开始装璜") print("装璜器2开始装璜") print("被装璜的函数") print("装璜器2完结装璜") print("装璜器1完结装璜")
上述第三步运行之后的代码,恰好与咱们的代码输入统一。
那当初再回到本大节一开始的案例,为何输入数据失落掉了。
import timedef go(func): def wrapper(x, y): s_time = time.perf_counter() func(x, y) e_time = time.perf_counter() print(f"函数{func.__name__}运行工夫是:{e_time-s_time}") return wrapperdef gogo(func): def wrapper(x, y): print("我是第二个装璜器") return wrapper@go@gogodef func(x, y): i = 0 while i < 1000: i += 1 print(f"x={x},y={y}")if __name__ == '__main__': func(33, 55)
在执行装璜器代码装璜之后,调用 func(33,55)
曾经切换为 go(gogo(func))
,运行 gogo(func)
代码转换为下述内容:
def wrapper(x, y): print("我是第二个装璜器")
在运行 go(wrapper)
,代码转换为:
s_time = time.perf_counter()print("我是第二个装璜器")e_time = time.perf_counter()print(f"函数{func.__name__}运行工夫是:{e_time-s_time}")
此时,你会发现参数在运行过程被丢掉了。
functools.wraps
应用装璜器能够大幅度提高代码的复用性,然而毛病就是原函数的元信息失落了,比方函数的 __doc__
、__name__
:
# 装璜器def logged(func): def logging(*args, **kwargs): print(func.__name__) print(func.__doc__) func(*args, **kwargs) return logging# 函数@loggeddef f(x): """函数文档,阐明""" return x * xprint(f.__name__) # 输入 loggingprint(f.__doc__) # 输入 None
解决办法非常简单,导入 from functools import wraps
,批改代码为下述内容:
from functools import wraps# 装璜器def logged(func): @wraps(func) def logging(*args, **kwargs): print(func.__name__) print(func.__doc__) func(*args, **kwargs) return logging# 函数@loggeddef f(x): """函数文档,阐明""" return x * xprint(f.__name__) # 输入 fprint(f.__doc__) # 输入 函数文档,阐明
基于类的装璜器
在理论编码中 个别 “函数装璜器” 最为常见,“类装璜器” 呈现的频率要少很多。
基于类的装璜器与基于函数的根本用法统一,先看一段代码:
class H1(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return '' + self.func(*args, **kwargs) + ''@H1def text(name): return f'text {name}'s = text('class')print(s)
类 H1
有两个办法:
__init__
:接管一个函数作为参数,就是待被装璜的函数;__call__
:让类对象能够调用,相似函数调用,触发点是被装璜的函数调用时触发。
最初在附录一篇写的不错的 博客,能够去学习。
在这里类装璜器的细节就不在开展了,等到前面滚雪球相干我的项目实操环节再说。
装璜器为类和类的装璜器在细节上是不同的,上文提及的是装璜器为类,你能够在思考一下如何给类增加装璜器。
内置装璜器
常见的内置装璜器有 @property
、@staticmethod
、@classmethod
。该局部内容在细化面向对象局部进行阐明,本文只做简略的备注。
@property
把类内办法当成属性来应用,必须要有返回值,相当于 getter
,如果没有定义 @func.setter
润饰办法,是只读属性。
@staticmethod
静态方法,不须要示意本身对象的 self
和本身类的 cls
参数,就跟应用函数一样。
@classmethod
类办法,不须要 self
参数,但第一个参数须要是示意本身类的 cls
参数。
这篇博客的总结
对于 Python 装璜器,网上的文章切实太太多了,学习起来并不是很难,真正难的是恰到好处的利用在我的项目中,心愿本篇博客能对你了解装璜器有所帮忙。
其余内容也能够查阅官网手册