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
  • 相关阅读:
    POJ3159 Candies —— 差分约束 spfa
    POJ1511 Invitation Cards —— 最短路spfa
    POJ1860 Currency Exchange —— spfa求正环
    POJ3259 Wormholes —— spfa求负环
    POJ3660 Cow Contest —— Floyd 传递闭包
    POJ3268 Silver Cow Party —— 最短路
    POJ1797 Heavy Transportation —— 最短路变形
    POJ2253 Frogger —— 最短路变形
    POJ1759 Garland —— 二分
    POJ3685 Matrix —— 二分
  • 原文地址:https://www.cnblogs.com/yqpy/p/8545767.html
Copyright © 2011-2022 走看看