zoukankan      html  css  js  c++  java
  • Python装饰器探险

    关于python装饰器的理解和用法,推荐廖雪峰老师这一篇博客以及知乎

    以下代码均已手动敲过,看完本篇内容,包你装饰器小成!

    装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码,这样是不科学的也是不现实的,因此就产生了装饰器,使得其满足:

    1. 不能修改被装饰的函数的源代码
    2. 不能修改被装饰的函数的调用方式
    3. 满足1、2的情况下给程序增添功能

    首先需要理解的是在Python中一切皆对象,函数也是对象,且函数对象可以赋值给变量,所以通过变量也能调用该函数。

    >>> def now():
    ...     return '2018-3-11'
    ...
    >>> f = now
    >>> f()
    2018-3-11

    然后需要理解的是函数后面加个()才是调用,也就是函数被执行了

    >>> type(f())
    <class 'str'>
    >>> type(f)
    <class 'function'>

    接着简单粗暴直接上例子

    如我我们想要给一个函数增加统计代码运行时间的功能,但又不能修改原函数,且不能修改原函数的调用方式,比如下面这样子:

    import time
    
    def test():
        time.sleep(2)
        print("test is running!")
    
    def deco(func):  
        start = time.time()
        func() #2
        stop = time.time()
        print(stop-start)
    
    deco(test) #1

    执行结果:

    test is running!
    2.001065731048584

    我们来看一下这段代码,在#1处,我们把test当作实参传递给形参func,即func=test。注意,这里传递的是地址,也就是此时func也指向了之前test所定义的那个函数体,可以说在deco()内部,func就是test。在#2处,把函数名后面加上括号,就是对函数的调用(执行它)。

    但是这修改了原函数的调用方式(deco(test)算怎么回事?),要求一定要是test()这样的调用方式,所以接着改进,用到了嵌套函数:

    import time
    
    def timer(func): #5
        def deco(): 
            start = time.time()
            func() #2
            stop = time.time()
            print(stop-start)
        return deco
    
    
    def test():
        time.sleep(2)
        print("test is running!")   
    test = timer(test) #6
    test() #7

    结果如下:

    test is running!
    2.0003929138183594

    首先,在#6处,把test作为参数传递给了timer(),此时,在timer()内部,func = test,接下来,定义了一个deco()函数,当并未调用,只是在内存中保存了,并且标签为deco。在timer()函数的最后返回deco()的地址deco。

    然后再把deco赋值给了test,那么此时test已经不是原来的test了,也就是test原来的那些函数体的标签换掉了,换成了deco。那么在#7处调用的实际上是deco()。

    那么这段代码在本质上是修改了调用函数,但在表面上并未修改调用方式,而且实现了附加功能。

    那么真正的装饰器也就是实现了以上的功能,只是用到了另一种语法功能--语法糖:

    import time
    
    def timer(func): #5
        def deco():  
            start = time.time()
            func()
            stop = time.time()
            print(stop-start)
        return deco
    
    @timer
    def test():
        time.sleep(2)
        print("test is running!")
    
    #test = timer(test) #6   
    #print(test)
    
    test() #7

    分析:这段代码跟上面的代码相比,其实是把test=timer(test)换成了@timer,所以说@timer的作用就是把test这个被装饰函数当作func参数传到装饰器timer里面,然后遇到了deco函数,先不执行这个函数(因为还没有调用),然后返回deco函数地址,这时候deco这个函数就给了test,接着执行test()的时候,就要执行deco函数了,注意deco函数里的func已经变成了原来那个test了

    执行结果:

    test is running!
    2.000576972961426

    接着是被装饰函数带参数的情况:

    import time
    
    def timer(func):
        def wrapper(*args,**kwargs):  
            start = time.time()
            res = func(*args,**kwargs)
            stop = time.time()
            print(stop-start)
            return res
        return wrapper
    
    @timer #相当于执行了语句test = timer(test)
    def test(a,b): #8
        time.sleep(2)
        #print("test is running!")
        return a+b
    a = test(1,2)
    print(a)

    实际的被装饰函数可能是有参数的,也可能是有返回值的,那么就应该把装饰器里嵌套的函数也带上参数,但是应该是可变参数*args和**kwargs,方便传给func,注意这里面func的返回值赋值给了res,而wrapper也是有返回值的,所以test(1,2)最后是有一个返回值3

    结果如下:

    2.0004124641418457
    3

    接着再看带参数的装饰器:

    假如说实际上有这样需求:对不同的函数进行不同功能的装饰,那么就需要知道对哪个函数进行哪个装饰,就需要给装饰器加上参数来辨认函数

     1 import time
     2 import functools
     3 
     4 def timer(parameter):
     5     def decorator(func):
     6         @functools.wraps(func)
     7         def wrapper(*args,**kwargs):
     8             if parameter =='task1': 
     9                 start = time.time()
    10                 res = func(*args,**kwargs)
    11                 stop = time.time()
    12                 print("the task1 run time is :",stop - start)
    13                 return res
    14             elif parameter =='task2':
    15                 start = time.time()
    16                 res = func(*args,**kwargs)
    17                 stop = time.time()
    18                 print("the task2 run time is :",stop - start)
    19                 return res
    20         return wrapper
    21     return decorator
    22 
    23 @timer(parameter='test1')
    24 def test1(a,b): #8
    25     time.sleep(2)
    26     #print("test is running!")
    27     return a+b
    28 
    29 @timer(parameter='test2')
    30 def test2(a,b): #8
    31     time.sleep(3)
    32     #print("test is running!")
    33     return a+b
    34 
    35 a = test1(1,2)
    36 b = test2(2,3)
    37 print(a,b,test1.__name__)

    代码分析:@timer(parameter='test1')相当于test1 = timer(parameter='test1')(test1),也就是说timer(parameter='test1')返回了decorator,timer(parameter)(test1)是返回了wrapper这个函数,接着wrapper变成了test1,最后调用test1的时候,也就是执行了wrapper

    .__name__方法是指查看原函数的名字

    注意:这里还涉及到了嵌套函数的知识点:函数只能调用和他同级或比它高级的变量和函数,这里wrapper可以调用func函数,也可以调用parameter参数,就是这个道理

    结果如下:

    the task1 run time is : 2.000300645828247
    the task2 run time is : 3.0010263919830322
    3 5 test1

    至此已经大功告成,但是第二行和第六行的代码是什么意思呢,原来如果不加这两句的话,被装饰函数的名字就变成了wrapper,不信请看:

     1 import time
     2 import functools
     3 
     4 def timer(parameter):
     5     def decorator(func):
     6         #@functools.wraps(func)
     7         def wrapper(*args,**kwargs):
     8             if parameter =='test1': 
     9                 start = time.time()
    10                 res = func(*args,**kwargs)
    11                 stop = time.time()
    12                 print("the task1 run time is :",stop - start)
    13                 return res
    14             elif parameter =='test2':
    15                 start = time.time()
    16                 res = func(*args,**kwargs)
    17                 stop = time.time()
    18                 print("the task2 run time is :",stop - start)
    19                 return res
    20         return wrapper
    21     return decorator
    22 
    23 @timer(parameter='test1')
    24 def test1(a,b): #8
    25     time.sleep(2)
    26     #print("test is running!")
    27     return a+b
    28 
    29 @timer(parameter='test2')
    30 def test2(a,b): #8
    31     time.sleep(3)
    32     #print("test is running!")
    33     return a+b
    34 
    35 a = test1(1,2)
    36 b = test2(2,3)
    37 print(a,b,test1.__name__)

    我把第六行注释掉之后,结果:

    the task1 run time is : 2.000321388244629
    the task2 run time is : 3.000973701477051
    3 5 wrapper

    所以为了保持原函数的纯洁,还是要加上这两句代码

    至此,装饰器至少已经入门了。

    人生苦短,何不用python
  • 相关阅读:
    C#计算两个日期之间相差的天数
    js字符串转时间
    mysql find_in_set 函数
    css控制网页所有图片当图片大于指定宽度图片等于指定宽度
    网页自动适应手机屏幕宽度的方法
    c# 生成指定范围的数字和字母组合随机数
    SqlServer 查看被锁的表和解除被锁的表
    C#隐情信息(银行账户,身份证号码,名字)中间部分特殊字符替换(*)
    c#检查SQL语法是否正确,不执行SQL语句
    sqlserver中的表值函数和标量值函数
  • 原文地址:https://www.cnblogs.com/yqpy/p/8545767.html
Copyright © 2011-2022 走看看