共计 5630 个字符,预计需要花费 15 分钟才能阅读完成。
函数装璜器
装璜器(Decorators)在 Python 中,次要作用是批改函数的性能,而且批改前提是不变动原函数代码,装璜器会返回一个函数对象,所以有的中央会把装璜器叫做“函数的函数”。
还存在一种设计模式叫做“装璜器模式”,这个后续的课程会有所波及。
装璜器调用的时候,应用 @
,它是 Python 提供的一种编程语法糖,应用了之后会让你的代码看起来更加 Pythonic
。
装璜器根本应用
在学习装璜器的时候,最常见的一个案例,就是统计某个函数的运行工夫,接下来就为你分享一下。
计算函数运行工夫:
import time
def fun():
i = 0
while i < 1000:
i += 1
def fun1():
i = 0
while i < 10000:
i += 1
s_time = time.perf_counter()
fun()
e_time = time.perf_counter()
print(f"函数 {fun.__name__} 运行工夫是:{e_time-s_time}")
如果你心愿给每个函授都加上调用工夫,那工作量是微小的,你须要反复的批改函数外部代码,或者批改函数调用地位的代码。在这种需要下,装璜器语法呈现了。
先看一下第一种批改办法,这种办法没有减少装璜器,然而编写了一个通用的函数,利用 Python 中函数能够作为参数这一个性,实现了代码的可复用性。
import time
def fun():
i = 0
while i < 1000:
i += 1
def fun1():
i = 0
while i < 10000:
i += 1
def 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 time
def 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
@go
def func():
i = 0
while i < 1000:
i += 1
@go
def func1():
i = 0
while i < 10000:
i += 1
if __name__ == '__main__':
func()
在上述代码中,留神看 go
函数局部,它的参数 func
是一个函数,返回值是一个外部函数,执行代码之后相当于给原函数注入了计算工夫的代码。在代码调用局部,你没有做任何批改,函数 func
就具备了更多的性能(计算运行工夫的性能)。
装璜器函数胜利拓展了原函数的性能,又不须要批改原函数代码,这个案例学会之后,你就曾经初步理解了装璜器。
对带参数的函数进行装璜
间接看代码,理解如何对带参数的函数进行装璜:
import time
def 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
@go
def 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)
此时如果咱们总结一下,就能失去论断了:应用带有参数的装璜器,是在装璜器里面又包裹了一个函数,应用该函数接管参数,并且返回一个装璜器函数。
还有一点要留神的是装璜器只能接管一个参数,而且必须是函数类型。
![Python 中级常识之装璜器,滚雪球学 Python](https://img-blog.csdnimg.cn/20210307141505987.png#pic\_center =300x)
多个装璜器
先临摹一下下述代码,再进行学习与钻研。
import time
def 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
def gogo(func):
def wrapper(x, y):
print("我是第二个装璜器")
return wrapper
@go
@gogo
def 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 time
def d1(func):
def wrapper1():
print("装璜器 1 开始装璜")
func()
print("装璜器 1 完结装璜")
return wrapper1
def d2(func):
def wrapper2():
print("装璜器 2 开始装璜")
func()
print("装璜器 2 完结装璜")
return wrapper2
@d1
@d2
def func():
print("被装璜的函数")
if __name__ == '__main__':
func()
上述代码运行的后果为:
装璜器 1 开始装璜
装璜器 2 开始装璜
被装璜的函数
装璜器 2 完结装璜
装璜器 1 完结装璜
能够看到十分对称的输入,同时证实 被装璜的函数 在最内层,转换成函数调用的代码如下:
d1(d2(func))
你在这部分须要留神的是,装璜器的 外函数 和内函数 之间的语句,是没有装璜到指标函数上的,而是在装载装璜器时的附加操作。
在对函数进行装璜的时候,外函数与内函数之间的代码会被运行。
测试成果如下:
import time
def d1(func):
print("我是 d1 内外函数之间的代码")
def wrapper1():
print("装璜器 1 开始装璜")
func()
print("装璜器 1 完结装璜")
return wrapper1
def d2(func):
print("我是 d2 内外函数之间的代码")
def wrapper2():
print("装璜器 2 开始装璜")
func()
print("装璜器 2 完结装璜")
return wrapper2
@d1
@d2
def 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 time
def 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
def gogo(func):
def wrapper(x, y):
print("我是第二个装璜器")
return wrapper
@go
@gogo
def 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
# 函数
@logged
def f(x):
"""函数文档,阐明"""
return x * x
print(f.__name__) # 输入 logging
print(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
# 函数
@logged
def f(x):
"""函数文档,阐明"""
return x * x
print(f.__name__) # 输入 f
print(f.__doc__) # 输入 函数文档,阐明
基于类的装璜器
在理论编码中 个别“函数装璜器”最为常见,“类装璜器”呈现的频率要少很多。
基于类的装璜器与基于函数的根本用法统一,先看一段代码:
class H1(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return ''+ self.func(*args, **kwargs) +''
@H1
def 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 装璜器,网上的文章切实太太多了,学习起来并不是很难,真正难的是恰到好处的利用在我的项目中,心愿本篇博客能对你了解装璜器有所帮忙。
其余内容也能够查阅官网手册