• P03-Python装饰器


    本文总结自oldboy python教学视频。

    一、前言

    1.装饰器本质上就是函数,功能是装饰其他函数,为其他函数添加附加功能。

    装饰器在装饰函数时必须遵循3个重要的原则:

    (1)不能修改被装饰的函数的源代码

    (2)不能修改被装饰的函数的调用方式

    (3)不能修改表装饰函数的返回结果

     

    2.实现装饰器的知识储备:

    (1)函数即“变量”

    关于函数即变量,我的理解如下所示,即当定义一个函数时,相当于在内存中划一块内存空间,里面存放函数的函数体,而函数名指向这块空间——这跟普通的函数定义如 a = 9527 是一样的。如此一来,函数名便可以跟普通变量一样,作为参数传给其他函数,也能在函数中作为结果被返回。

    (2)高阶函数

    a.把一个函数名当做实参传给另一个函数(在不修改被装饰函数源代码的情况下为其添加功能)

    或:

    b.返回值中包含函数名(不修改函数的调用方式)

    (3)嵌套函数

    所谓嵌套函数,即在一个函数内定义另外一个函数(注意是定义,而不是调用)。

     

    3.高阶函数 + 嵌套函数 => 装饰器

     

     

    二、装饰器应用

    1.被装饰函数没有参数

    假设被装饰的函数为test1(),此时的test1()没有形参,默认返回NULL;装饰器timmer完成的附加功能是计时。

    # -*- coding:utf-8 -*-
    #Author:Suxy
    
    import time
    
    def timmer(func):
        def wrapper():
            start_time = time.time()
            func()
            stop_time = time.time()
            print("the func run time is %s" %(stop_time - start_time))
        return  wrapper
    
    @timmer  #test1 = timmer(test1),即test1 = wrapper
    def test1():
        time.sleep(1)
        print("in the test1")
    
    test1()

    输出:

    C:appAnaconda3python.exe E:/python_workspace/pure_python/day4/decorator.py
    in the test1
    the func run time is 1.0003414154052734

    Process finished with exit code 0

     注:在函数test1()的上面加上@timmer,作用相当于test1 = timmer(test1),又因为timmer函数中返回wrapper,所以有test1 = timmer(test1) = wrapper,因此最后一行代码在使用test()方式调用函数的时候,实际调用的是wrapper(),而wrapper中的变量func接收了timemr(test1)传进来的test1,所以实际调用时,先执行wrapper中附加的功能(计时),之后执行test1()本身的功能。

     2.被装饰函数带有参数

    装饰器不仅要能装饰无参函数,还得能装饰带参函数。这里以只带一个参数的函数test2(name)为例。此时若是不修改上面装饰器的任何代码便用来装饰test2(name),则会报错,具体如下:

    # -*- coding:utf-8 -*-
    #Author:Suxy
    
    import time
    
    def timmer(func):
        def wrapper():
            start_time = time.time()
            func()
            stop_time = time.time()
            print("the func run time is %s" %(stop_time - start_time))
        return  wrapper
    
    @timmer  #test1 = timmer(test1),即test1 = wrapper
    def test1():
        time.sleep(1)
        print("in the test1")
    
    @timmer
    def test2(name):
        time.sleep(1)
        print("in the test2, ", name)
    
    test1()
    test2("suxy")

     输出:

    C:appAnaconda3python.exe E:/python_workspace/pure_python/day4/decorator.py
    Traceback (most recent call last):
      File "E:/python_workspace/pure_python/day4/decorator.py", line 25, in <module>
        test2("suxy")
    TypeError: wrapper() takes 0 positional arguments but 1 was given
    in the test1
    the func run time is 1.000788688659668
    
    Process finished with exit code 1

     可见,此时装饰器timmer用于装饰test1()时没有问题,但是装饰test2()时报错了:wrapper()函数接受0个位置参数,但是在调用时却传了1个。

    由第1步可知,在使用装饰器timmer装饰函数test2之后,调用test2(“suxy”)就相当于调用wrapper(“suxy”)函数,而当前的wrapper()函数是不接收参数的。

    若需要timme装饰器能够同时装饰test1和test2函数,则需要对timmer装饰器进行修改——不过也不能简单地将timmer()中wrapper()函数的定义修改为wrapper(name),因为这样一来timmer装饰test1就会出错,此外,timmer装饰器有可能还要装饰其他的比如带有默认参数、关键字参数等的函数,因此,wrapper函数的定义应修改为wrapper(*args, **kwargs),并且wrapper中func的调用方式也得进行相应的修改func(*args, **kwargs),这样一来,无论被装饰的函数是无参函数、默认参数、关键字参数还是如*args列表参数、**kwargs字典参数,装饰器都能进行装饰。如下:

    # -*- coding:utf-8 -*-
    #Author:Suxy
    
    import time
    
    def timmer(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            func(*args, **kwargs)
            stop_time = time.time()
            print("the func run time is %s" %(stop_time - start_time))
        return  wrapper
    
    @timmer  #test1 = timmer(test1),即test1 = wrapper
    def test1():
        time.sleep(1)
        print("in the test1")
    
    @timmer
    def test2(name):
        time.sleep(1)
        print("
    in the test2, ", name)
    
    test1()
    test2("suxy")

     输出:

    C:appAnaconda3python.exe E:/python_workspace/pure_python/day4/decorator.py
    in the test1
    the func run time is 1.000464916229248
    
    in the test2,  suxy
    the func run time is 1.0004689693450928
    
    Process finished with exit code 0

     3.上面第2步所说的装饰器已经能够装饰一般的带参函数了,不过,若是被装饰函数具有返回值,则上面的装饰器就有问题了。如下(为缩减篇幅,暂将函数test1省略掉):

    # -*- coding:utf-8 -*-
    #Author:Suxy
    
    import time
    
    def timmer(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            func(*args, **kwargs)
            stop_time = time.time()
            print("the func run time is %s" %(stop_time - start_time))
        return  wrapper
    
    @timmer
    def test2(name):
        time.sleep(1)
        print("
    in the test2, ", name)
        return 9527
    
    print(test2("suxy"))

     输出:

    C:appAnaconda3python.exe E:/python_workspace/pure_python/day4/decorator.py
    
    in the test2,  suxy
    the func run time is 1.0003151893615723
    None
    
    Process finished with exit code 0

     可见,函数test2()中明明返回了9527,但是最后输出确是None,违反了装饰器不能修改被装饰函数的返回结果的原则。这是因为装饰器的wrapper中调用func(*args, **kwargs)函数(注: func(*args, **kwargs) = test2(*args, **kwargs))时,只是进行直接的调用,而没有将调用的结果返回。可以将装饰器代码修改为 return func(*args, **kwargs),或者先用res = func(*args, **kwargs)将函数执行结果保存到res中,最后再返回res。如下:

    # -*- coding:utf-8 -*-
    #Author:Suxy
    
    import time
    
    def timmer(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            res = func(*args, **kwargs)
            stop_time = time.time()
            print("the func run time is %s" %(stop_time - start_time))
            return res
        return  wrapper
    
    
    @timmer
    def test2(name):
        time.sleep(1)
        print("
    in the test2, ", name)
        return 9527
    
    print(test2("suxy"))

     输出:

    C:appAnaconda3python.exe E:/python_workspace/pure_python/day4/decorator.py
    
    in the test2,  suxy
    the func run time is 1.0006110668182373
    9527
    
    Process finished with exit code 0

    4.到上一步,装饰器已经可以装饰绝大部分的函数了。但是目前的装饰器只能简单的接受一个参数,即被装饰函数的函数名,无法接受其他的参数。若是有其他复杂的需求,比如接受func_type字段,根据func_type的不同而做不同的输出,则目前的装饰器无法完成这个功能。

    # -*- coding:utf-8 -*-
    #Author:Suxy
    
    import time
    
    def before_timmer(func_type):
        if func_type == "type1":
            print("It is type 1")
        elif func_type == "type2":
            print("It is type 2")
        else:
            print("Another type")
    
        def timmer(func):
            def wrapper(*args, **kwargs):
                start_time = time.time()
                res = func(*args, **kwargs)
                stop_time = time.time()
                print("the func run time is %s" % (stop_time - start_time))
                return res
            return wrapper
        return timmer
    
    @before_timmer(func_type = "type2")
    def test2(name):
        time.sleep(1)
        print("
    in the test2, ", name)
        return 9527
    
    test2("suxy")

     输出:

    C:appAnaconda3python.exe E:/python_workspace/pure_python/day4/decorator.py
    It is type 2
    
    in the test2,  suxy
    the func run time is 1.0008769035339355
    
    Process finished with exit code 0

     仔细对比这里和3中装饰器代码的区别可以发现,这一步相当于又新建了一个装饰器before_timmer将原先的装饰器timmer包了起来,并将timmer函数作为结果返回,使得可以在before_timmer装饰器中先完成判断功能,再调用timmer计时功能。

    此时对于test1()函数定义上的@before_timmer(func_type = "type1"),我的理解是:test2 = before_timmer(func_type = “type2”),又因为before_timmer中有return timmer,所以test2 = before_timmer(func_type = “type2”) = timmer(test2),同时由于timmer中有return wrapper,所以 test2 = before_timmer(func_type = “type2”) = timmer(test2) = wrapper,因此,当调用test2(“suxy”)时,还是最后还是相当于调用了wrapper(“suxy”)。

  • 相关阅读:
    数组下标索引的循环原来可以这样写
    移位运算>>与>>>
    java调用redis的多种方式与心得
    $.ajax传输js数组,spring接收异常
    div背景css样式笔记
    js监听网页页面滑动滚动事件,实现导航栏自动显示或隐藏
    设置系统时区
    安装与配置文本编辑器vim
    添加阿里云数据源
    spring controller获取web前端post数据乱码解决
  • 原文地址:https://www.cnblogs.com/suhaha/p/8689634.html
Copyright © 2020-2023  润新知