乐趣区

关于python:抬抬小手学Python-Python-中级知识之装饰器

函数装璜器

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

退出移动版