python——装饰器详解

写在前面

装饰器需要一点函数的知识:在函数中定义函数;从函数中返回函数;将函数作为参数传给另外一个函数。我假设读者有这样的先验知识。

1. 什么是装饰器

装饰器就是用来包装函数的,在不能改变原有函数的功能属性的基础上,可以再增加一些功能

例如我装修一个房子,如果不隔音,我在墙上加一层隔音板,却不能把墙拆了,换成隔音材质的。

程序中也是一样的,不会对原有函数造成改变,还要添加新的功能,调用函数时的接口没有变化

2. 用装饰器给函数增加计时的功能

2.1 方案一

  • 就是最原始的python代码
import time

def index():
    time.sleep(2)

start_time = time.time()

index()

end_time = time.time()

print(end_time - start_time)
  • 思考一个问题,如果我们的函数有很多个,每一个函数都需要计时,这时候,方案一的代码写下来会变得非常的冗余,所以我们需要一点改进

2.2 方案二——函数作为参数

  • 这里我们可以实现任何函数的计时功能,但是有一个问题,我们说到装饰器不改变原有函数的调用接口(方案二中并没有调用index函数实现,而是调用了calculate_time)。(举个例子:假如玩游戏时我们习惯用“WASD”操作方向,突然变成了“ZXCS”,那么我们肯定就不太习惯要调回来)。我们需要再方案三中解决这个问题
def calculate_time(f):
    start_time = time.time()

    f()

    end_time = time.time()

    print(end_time - start_time)

calculate_time(index)

2.3 方案三——使用闭包

  • 我们使用闭包,这样就可以不改变原函数的接口了
def calculate_time(f):
    
    def inner():
        start_time = time.time()

        f()

        end_time = time.time()

        print(end_time - start_time)
    return inner

index = calculate_time(index)
index()

2.4 方案四——装饰器

  • 装饰器的作用就相当于下面这行代码,语法为@装饰器
    index = calculate_time(index)

  • 值得注意的是,我们需要先定义装饰器,再定义需要被装饰的函数,不能反。

def calculate_time(f):
    
    def inner():
        start_time = time.time()

        f()

        end_time = time.time()

        print(end_time - start_time)
    return inner

@calculate_time # 等价于index = calculate_time(index),翻译为用@后面的函数去装饰下面的函数
def index():
    time.sleep(2)
    
index()
  • 该方案存在一点小瑕疵:当我们使用魔方函数__name__时,发现index.__name__返回的结果为inner,即
print(index.__name__)

# result
inner
  • 这并不是我们想要的,Ouput输出应该是"index"。这里的函数被inner替代了。它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使functools.wraps:

2.5 方案五——考虑魔方函数__name__

  • 注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。
  • 在此方案中,我们又加入了一个新功能,返回ret值,给index函数加了一个返回值
from functools import wraps
def calculate_time(f):
    @wraps(f)
    def inner():
        start_time = time.time()

        ret = f()

        end_time = time.time()

        print(end_time - start_time)
        return ret
    return inner

@calculate_time
def index():
    time.sleep(2)
    return "index"
    
index() # 输出时间和字符串"index"
print(index.__name__)  # index

2.6 最终版——适配多参数

  • 很多情况下,我们需要让装饰器去装饰多个参数的函数,这时候需要用到多值参数
  • 这里我们定义了需要被修饰的add函数,返回两个数之和。
from functools import wraps
def calculate_time(f):
    @wraps(f)
    def inner(*args,**kwargs):
        start_time = time.time()

        ret = f(*args,**kwargs)

        end_time = time.time()

        print(end_time - start_time)
        return ret
    return inner

@calculate_time
def add(m,n):
    time.sleep(2)
    return m + n
    
add(1,2)

猜你喜欢

转载自blog.csdn.net/weixin_44441131/article/details/107098419