函数装璜器

装璜器(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)

此时如果咱们总结一下,就能失去论断了:应用带有参数的装璜器,是在装璜器里面又包裹了一个函数,应用该函数接管参数,并且返回一个装璜器函数。
还有一点要留神的是装璜器只能接管一个参数,而且必须是函数类型。
![Python 中级常识之装璜器,滚雪球学 Python](https://img-blog.csdnimg.cn/20210307141505987.png#pic\_center =300x)

多个装璜器

先临摹一下下述代码,再进行学习与钻研。

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 装璜器,网上的文章切实太太多了,学习起来并不是很难,真正难的是恰到好处的利用在我的项目中,心愿本篇博客能对你了解装璜器有所帮忙。
其余内容也能够查阅官网手册