一.什么是装饰器?

本质是 一个函数。装饰 指为其他函数添加附加功能。

二.装饰器的两个原则(自己写一个功能,为别的已经存在的函数装饰上.那么原函数就在不改变源代码和调用方式的前提下,增加了新的功能)

1.不修改被修饰函数的源代码

2.不修改被修饰函数的调用方式

(1)为函数添加功能的方法,自己加模块

# def calc(i):
#     res=0
#     for i in  i:
#         res+=i
#     return res
# print(calc(range(100)))
#现在新加功能,计算该函数整体运行的时间
import time
def calc(i):
    starttime=time.time()
    res=0
    for i in  i:
        res+=i
    stoptime=time.time()
    print("yunxingshijian=%s"%(stoptime-starttime))
    return res
print(calc(range(100)))

  

(2)如果有很多很多的函数需要让你统计运行时间呢?你就不能一个一个写了吧!这时候就要运用到装饰器的理论。

 

三.装饰器的知识储备

装饰器=高阶函数+函数嵌套+闭包

(1)高阶函数

  函数接受的参数是函数名

  函数的返回值是函数名

  满足上述两个条件其一,就可以称作高阶函数。

import  time
def foo():
    time.sleep(2)
    print("hello")
def test(func):           #定义了一个高阶函数,函数接受的参数是函数名。
    starttime=time.time()
    func()              #执行这个函数
    stoptime=time.time()
    print("yunxingshijian=%s"%(stoptime-starttime))
test(foo)             

#foo()是原来的函数,test(func)是一个(缺点功能的)装饰器,在没有改变foo源代码的前提下,实现了统计foo运行时间的功能
#但是这样改变了函数的调用方式,因为原来是直接调用foo(),现在是test(foo).之后使用foo并且想统计运行时间的情况下,必须使用这种调用方式

  

def foo():
    print("from foo")
def test(func):
    return foo
foo=test(foo)               #返回值是一个函数,若test是一个实现某种特定功能的函数,那么通过返回值是函数,可以不改变函数的调用方式
foo()                       #不加装饰器之前,也是通过代码"foo()"运行,添加了之后还是通过这种方式执行

 

###综合以上两种方法,既不改变源代码,也不改变调用方式的代码

def foo():
    time.sleep(1.5)
    print("from foo")
def test(func):
    starttime = time.time()
    func()
    stoptime = time.time()
    print("tsss%s" % (stoptime - starttime))
    return func

foo=test(foo)
foo()

 

👆👆👆👆👆👆👆以上代码    多运行了一步在test()里运行了一次,之后运行return的值的时候又调用了一次。(不合格)

结论:只通过高阶函数无法实现装饰器的功能,还需要以下两种功能!!

 

(2)函数嵌套

  含义:在函数的定义里面再定义一个函数

(3)闭包(函数闭包装饰器的基本实现)

#装饰器的框架 1.高阶函数,函数括号里的东西是函数,返回值是函数 2.函数嵌套:函数里面定义一个函数 3.闭包
def timer(func):
    def wrapper():           #嵌套一个函数
        print(func)       #获得函数地址
        func()
    return wrapper          #return的是嵌套的函数名

 

 综合实例:通过装饰器统计函数的运行时间

import time
def timer(func):
    def wrapper():
        starttime=time.time()
        func()
        stoptime=time.time()
        print("yunxingle%s"%(stoptime-starttime))
    return wrapper
def test():
    time.sleep(3)
    print("运行完毕")
res=timer(test)
res()             #但是这样还存在一些问题,本来应该直接调用test(),现在是res(),应该怎样改正呢????

这样叫就可以通过调用test()实现为函数计时的功能👇

import time
def timer(func):
    def wrapper():
        starttime=time.time()
        func()
        stoptime=time.time()
        print("yunxingle%s"%(stoptime-starttime))
    return wrapper
def test():
    time.sleep(3)
    print("运行完毕")
test=timer(test)             #赋值操作
test()

 

 但是在倒数第二步的时候进行了一个赋值操作。你可以对每一个函数都进行赋值吗?显然不会,那怎么办呀?

import time
def timer(func):
    def wrapper():
        starttime=time.time()
        func()
        stoptime=time.time()
        print("yunxingle%s"%(stoptime-starttime))
    return wrapper
@timer   ##就相当于test=timer(test)   相当于把目标函数传给装饰器,在将装饰器返回为目标函数的地址  !!注意写的位置
def test():
    time.sleep(3)
    print("运行完毕")
# test=timer(test)
# test()
test()

 

补充:以上函数的返回值的问题

(1)

import time
def timer(func):
    def wrapper():
        starttime=time.time()
        func()
        stoptime=time.time()
        print("yunxingle%s"%(stoptime-starttime))
        return "wrapper的返回值"
    return wrapper
@timer   ##就相当于test=timer(test)   相当于把目标函数传给装饰器,在将装饰器返回为目标函数的地址  !!注意写的位置
def test():
    time.sleep(3)
    print("运行完毕")
    return "test的返回值"
# test=timer(test)
# test()
res=test()          #注意,这里运行的不是真正的test,而是通过@timer操作运行的wrapper函数,所以返回值应该是wrapper的返回值
print(res)           #返回值为wrapper的返回值

 

 

 (2)如果我们要在最后返回的是本来的那个函数的返回值应该在哪里修改?

import time
def timer(func):
    def wrapper():
        starttime=time.time()
        res=func()
        stoptime=time.time()
        print("yunxingle%s"%(stoptime-starttime))
        return res
    return wrapper
@timer   ##就相当于test=timer(test)   相当于把目标函数传给装饰器,在将装饰器返回为目标函数的地址  !!注意写的位置
def test():
    time.sleep(3)
    print("运行完毕")
    return "test的返回值"
res=test()          
print(res)

在代码红色的部分进行了修改

 

 函数闭包加参数:(被修饰的函数里面有参数)

import time
def timer(func):
    def wrapper(name,age):         ###这里需要传入值的原因是,你在函数的最后(倒数第二行)的时候其实是运行了wrapper函数,而你给她传入了两个值,所以在定义的时候也应该是两个值
        starttime=time.time()
        res=func(name,age)              ###实际上在运行test,所以需要传入值
        stoptime=time.time()
        print("yunxingle%s"%(stoptime-starttime))
        return res
    return wrapper
@timer   ##就相当于test=timer(test)   相当于把目标函数传给装饰器,在将装饰器返回为目标函数的地址  !!注意写的位置
def test(name,age):               #test里面需要传参数了
    time.sleep(3)
    print("运行完毕 nameis%s ageis %s"%(name,age))
    return "test的返回值"
res=test("yxz",18) 
print(res)

 

但是存在这样一个问题,就是我想给test1()、test()两个函数分别加上修饰器,但是,两个函数一个需要传入两个参数,一个需要传入三个,这样你在wrapper的时候,到底要写几个参数呢?就是以下代码中的矛盾

 

import time
def timer(func):
    def wrapper(name,age):              #这里wrapper犯了老大难,你要修饰两个函数,这里括号里到底写几个值?
        starttime=time.time()
        res=func(name,age)
        stoptime=time.time()
        print("yunxingle%s"%(stoptime-starttime))
        return res
    return wrapper
@timer   
def test(name,age):                    #两个参数
    time.sleep(1.5)
    print("运行完毕 nameis%s ageis %s"%(name,age))
    return "test的返回值"
@timer
def test1(name,age,gender):           #三个参数
    time.sleep(1)
    print("运行完毕 nameis%s ageis %s genderis%s"%(name,age,gender))
    return "test的返回值"
res=test("yxz",18)
print(res)
res1=test1("yxz",18,"male")
print(res)

 

 

 👇解决方法:这样两个函数都得到了统计计算时间的修饰

import time
def timer(func):
    def wrapper(*args,**kwargs):              #这里这么写,就可以接受任意可变长度的参数
        starttime=time.time()
        res=func(*args,**kwargs)
        stoptime=time.time()
        print("yunxingle%s"%(stoptime-starttime))
        return res
    return wrapper
@timer
def test(name,age):                    #两个参数
    time.sleep(1.5)
    print("运行完毕 nameis%s ageis %s"%(name,age))
    return "test的返回值"
@timer
def test1(name,age,gender):           #三个参数
    time.sleep(1)
    print("运行完毕 nameis%s ageis %s genderis%s"%(name,age,gender))
    return "test1的返回值"
res=test("yxz",18)
print(res)
res1=test1("yxz",18,"male")
print(res)

#输出结果:

运行完毕 nameisyxz ageis 18
yunxingle1.5001637935638428
test的返回值
运行完毕 nameisyxz ageis 18 genderismale
yunxingle1.0148701667785645
test的返回值

 

 重要补充:解压序列:

交换a,b的值

 

a=1
b=2
a,b=b,a
print(a,b)