Python之装饰器如何使用

2020/09/27 10:39

装饰器的使用

来看下面的代码:

    import time
    import random
    
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    index()

index函数的作用是程序在随机睡眠1到5秒之后,打印一句话。现在想为index函数添加一个新功能:统计index函数的运行时间,该怎么做呢??

修改index函数如下:

    import time
    import random
    
    def index():
        start_time=time.time()
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
        end_time=time.time()
        print("cost time: %s" %(end_time - start_time))
    
    index()

运行程序,执行结果如下:

welcome to index page
cost time: 2.000999927520752

可以看到,为index函数添加新功能确实实现了,但是却违反了开放封闭原则。在符合开放封闭原则的前提下,如果想为index函数添加新功能,此时就要使用装饰器了。

修改代码

  import time
  import random
    
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    def timmer():
        def inner():
            start_time=time.time()
            index()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    f=timmer()
    f()

运行程序,查看执行结果

welcome to index page
run time: 1.0

从程序执行结果可以看出,index函数的运行时间已经被统计出来了,但是查看源码可以知道,index函数的源码确实没有被修改,但是index的调用方式被修改了。而且还有一个问题就是,timmer这个装饰器只能被用来装饰index这个函数,如果以后想统计别的函数的运行时间,又要重新定义别的装饰器,这样也太不灵活了。

修改上面的代码

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
        
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    index=timmer(index)
    index()

运行程序,查看程序执行结果

welcome to index page
run time: 4.0

可以看到,index函数的源代码没有被修改,index函数的调用方式也没有改变,但是依然为index函数添加了统计时间的功能,这里使用的就是装饰器了。

来分析下上面代码的执行流程:

    1.导入time和random模块,定义index函数和timmer函数。

    2.把原始的index函数的内存地址作为参数传给timmer函数。

    3.timmer函数内部嵌套定义一个函数inner,然后返回inner函数的内存地址。

    4.timmer函数执行完成,返回timmer函数的内部函数inner的内存地址,然后把inner的内存地址赋值给index变量。

    5.index是inner函数的内存地址,index变量加括号运行,实际上就是在运行inner函数。

    6.运行inner函数,定义程序开始时间。

    7.执行timmer函数的变量func,在第2步知道,func这个变量就是index的内存地址,所以这里实际上是执行被装饰过后的index函数。

    8.index函数执行完成,定义程序的终止时间。

    9.统计并打印整个程序的执行过程中所花费的时间。

这就是装饰器装饰index函数的执行流程。

相关推荐:《Python视频教程》

装饰器的简化使用

现在我又有另外一个函数home,现在我也想统计home函数的运行时间,可以把代码修改如下:

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    def home():
        time.sleep(random.randrange(1,5))
        print("welcome to home page")
        
    index=timmer(index)
    index()
    
    home=timmer(home)
    home()

运行程序,执行结果如下:

welcome to index page
run time: 3.0 
welcome to home page
run time: 4.0

可以看到,每次调用统计程序运行时间的装饰器timmer,都要先把被调用的函数的函数名作为参数传给timmer装饰器,然后再把timmer装饰器的执行结果赋值给被调用的函数名本身,最后才能调用被装饰的函数,太麻烦了有没有??

其实python中的装饰器可以简化成下面的格式:

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    @timmer
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    @timmer
    def home():
        time.sleep(random.randrange(1,5))
        print("welcome to home page")
    
    index()
    home()

程序执行结果:

welcome to index page
run time: 2.0 
welcome to home page
run time: 4.0

可以看出,使用@加装饰器名添加到被装饰对象的上方的方式也可以为一个函数添加装饰器中定义的功能。

多个装饰器的定义与调用

在上面的例子里,定义并调用了一个统计程序运行时间的装饰器timmer,如果现在想为index函数添加一个用户认证的功能,可以定义一个名为auth的装饰器。

    import time
    import random
    
    def auth(func):
        def wrapper():
            while True:
                user=input("Input your username>>>:").strip()
                pwd=input("Input your password>>>:").strip()
                if user== "abcd" and pwd == "abcd1234":
                    print("login successful")
                    func()
                    break
                else:
                    print("login error")
        return wrapper
    
    @auth
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    index()

运行程序:

    Input your username>>>:abcd             # 先输入错误的用户名和密码
    Input your password>>>:1234
    login error                             # 提示用户输入错误,登录失败
    Input your username>>>:abcd             # 让用户再次输入用户名和密码
    Input your password>>>:abcd1234
    login successful                        # 登录成功
    welcome to index page                   # 执行index函数

从程序执行结果可以看出,用户登录密码验证的装饰器auth已经定义并被成功调用了。

如果想为index函数添加用户认证的功能,又想统计index函数执行时间的功能,在使用装饰器的情况下该怎么调用呢?

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    def auth(func):
        def wrapper():
            while True:
                user=input("Input your username>>>:").strip()
                pwd=input("Input your password>>>:").strip()
                if user== "abcd" and pwd == "abcd1234":
                    print("login successful")
                    func()
                    break
                else:
                    print("login error")
        return wrapper
    
    @timmer
    @auth
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    index()

在上面的代码里,为index函数添加了两个装饰器,现在有一个问题,就是这两个装饰器究竟哪个先被调用,哪个后被调用呢??

来分析一下,如果timmer装饰器先被调用,那么程序就会先执行timmer装饰器,然后再执行auth装饰器,提示输入用户名和密码,这样一来timmer装饰器统计的时间就会包括输入用户名和密码的时间,这个时间会远远大于index函数睡眠的2秒种;如果auth装饰器先被调用,timmer装饰器后被调用,那么timmer装饰器统计的运行时间就应该只包括index函数的执行时间值应该在2秒多一点点的时间范围内。

运行程序,先输入错误的用户名和密码以使用程序的执行时间加长。

Input your username>>>:abcd
Input your password>>>:abcd
login error
Input your username>>>:abcd
Input your password>>>:abcd1234
login successful
welcome to index page
run time: 12.759000062942505

从程序的执行结果可以知道,程序是先运行timmer装饰器,然后才运行auth装饰器,所以timmer统计的时间就包括了用户认证的时间,所以timmer统计到的程序运行时间远远大于index睡眠的2秒钟。

所以这里得出一个结论:

当一个函数同时被两个装饰器装饰时,加上函数最上面的装饰器先执行,加在下面的装饰器先装饰。

把上面例子里的timmer装饰器和auth装饰器位置互换一下。

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    def auth(func):
        def wrapper():
            while True:
                user=input("Input your username>>>:").strip()
                pwd=input("Input your password>>>:").strip()
                if user== "abcd" and pwd == "abcd1234":
                    print("login successful")
                    func()
                    break
                else:
                    print("login error")
        return wrapper
    
    @auth
    @timmer
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    index()

运行index函数,依然先输入错误的用户名和密码,增加用户认证的时间。

Input your username>>>:abcd
Input your password>>>:abcd
login error
Input your username>>>:abcd
Input your password>>>:abcd1234
login successful
welcome to index page
run time: 2.0

可以看到,这次timmer统计到的时间只包含index函数的运行时间,不包含用户进行认证的时间。

来分析一下上面例子中,index函数被timmer装饰器和auth装饰器装饰的代码装饰流程。

    @auth           # index=auth(timmer(index))
    @timmer         # index=timmer(index)
    def index():
        time.sleep(2)
        print("welcome to index page")

在上面得出结论,一个函数同时被两个装饰器时,加在下面的装饰器先装饰:

1.timmer装饰器装饰原始的index,可以写成:index=timmer(index)。

2.在timmer装饰器中,timmer装饰器实际上是返回inner的内存地址,所以在这里,index=inner。

3.timmer装饰器装饰完成后,由auth装饰器来装饰,此时可以写成index=auth(index)。

4.这里auth括号里的index已经不再是原始index函数,而是已经被timmer装饰过后的index了,所以index=auth(timmer(index))。

5.又因为timmer装饰的结果等于inner函数的内存地址,所以:index=auth(inner)。

至此,两个装饰器的装饰过程已经知道了,来看程序的执行过程:

6.程序先执行auth装饰器,进入用户认证,请用户输入用户名和密码。

7.用户输入正确的用户名和密码后,开始执行func函数,也已经上面分析的inner函数。

8.timmer装饰器先定义程序的开始运行时间,然后运行func函数,也就是原生的index函数。

9.index函数先睡眠2秒,然后执行print语句,再定义程序的结束时间。

10.最后统计并打印程序的运行时间,至此程序运行完毕。

所以这里用户输入用户名和密码的时间不会被timmer装饰器统计在内。

相关推荐:

Python之装饰器简介

免费直播

    精选课程 更多

    注册电脑版

    版权所有 2003-2020 广州环球青藤科技发展有限公司