流畅的python学习笔记第七章:装饰器

mac2022-06-30  63

装饰器就如名字一样,对某样事物进行装饰过后然后返回一个新的事物。就好比一个毛坯房,经过装修后,变成了精装房,但是房子还是同样的房子,但是模样变了。 我们首先来看一个函数。加入我要求出函数的运行时间。一般来说代码写成如下。但是我有很多个函数都要计算运行时间。每个函数里面都要写一个计时的过程是一件很麻烦的事。 def target():     start=time.time()     print 'running target()'     end=time.time()     print end-start 我们把函数改造一下:增加一个函数decorate。在运行的时候调用decorate(target)。将函数名称传入decorate中。并在decorate中运行target,并得出计算的结果。那么在target函数中就不需要计算时间的过程。借用前面的装修房子的例子。当计算代码运行的代码写在各自函数里面的时候,就好比业主自己在装修房子,这样做费力又费时。那么decorate就好比是一个装修公司。只要向它传递装修需求,也就是参数,那么装修公司就会自动到家里来装修 def target():     print 'running target()'def decorate(fun):     start=time.time()     fun()     end=time.time()     print end-startif __name__ == "__main__":     decorate(target) 但是这样也会有个问题,那就是如果我有100个target函数,分别是target1,target2,target3...target100. 如果我要计算出每个函数的运行时间 那么我的调用将会是下面的方式,得写上100个这样的调用函数。这样也挺麻烦的。有没有方法让每次target函数运行的时候自动计算时间呢。 decoreate(target1) decoreate(target2) . . . decoreate(target100)   这里就引出了装饰器。代码如下,在target的前面加上@decorate。这个调用等于target=decorate(target)。多么的简洁 def decorate(fun):     start=time.time()     fun()     end=time.time()     print end-start @decoratedef target():     print 'running target()' 回到我们刚才的疑问。有100个target函数需要计算运行时间。有没有简洁的调用方法。有了装饰器,我们的代码就可以简化成 @decoratedef target1():     print 'running target()' . . . @decoratedef target100():     print 'running target100()' 这样就省去了100次的decorate(target)的调用,在每个函数前面加上@decorate就可以了,形象点说,这就好比川剧中的变脸,有了装饰器,函数就可以变成不同的脸。既然变了脸,那么函数本身比如target变化了么。答案是变化了的。被装饰的函数会被替换,来看下面的代码: def decorate(fun):     def inner():         start=time.time()         fun()         end=time.time()         print end-start     return inner @decoratedef target():     print 'running target()'if __name__ == "__main__":     target()     print target E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py running target() 0.0 <function inner at 0x017D11B0> 我们看到print target的时候结果为function inner at 0x017D11B0。target变成了inner的引用.来看下整个过程。 1 target=decorate(target) 2 decorate(target)返回的是inner 3 最终的结果是target=inner. 因此target变成了innter的引用。如果想保持target的名称。那么装饰函数就要修改如下:增加@wraps后,被装饰的函数将会保持原型 from functools import wraps def decorate(fun): @wraps(fun)     def inner():         start=time.time()         fun()         end=time.time()         print end-start     return inner   我们来看下装饰器的运行顺序呢:将代码稍微修改一下: def decorate(fun):     print 'decoreate working'     def inner():         start=time.time()         fun()         end=time.time()         print end-start     return inner @decoratedef target():     print 'running target()'if __name__ == "__main__":     target() decorate中新增了一条打印语句:print 'decoreate working'E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py decoreate working running target() 0.0 从执行结果中可以看到首先执行打印了decoreate working,然后开始执行具体的操作函数。 我们来看一个更复杂的例子。 registry=[]   #保存被register引用的函数引用def register(func): print 'running register(%s)' % func     registry.append(func)     return func @registerdef f1():     print 'running f1()'@registerdef f2():     print 'running f2()'def f3():     print 'running f3()'def main():     print 'running main()'     print 'registry->%s' % registry     f1()     f2()     f3()if __name__ == "__main__":     main() E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py running register(<function f1 at 0x01714270>) running register(<function f2 at 0x017142F0>) running main() registry->[<function f1 at 0x01714270>, <function f2 at 0x017142F0>] running f1() running f2() running f3() 从执行的步骤来看,首先register在模块中其他函数之前运行了2次。然后在开始执行main以及后面被装饰的函数。所以装饰器在导入模块时立即运行。而被装饰的函数只在明确调用是运行。   继续来看下装饰器的不同用法: 1 被装饰函数带参数: def decorate(fun):     def inner(a,b):         print 'before function be called'         ret=fun(a,b)         print 'after function be called'         return ret     return inner @decoratedef target(a,b):     print 'The parameter is %s,%s' % (a,b)     return a+b if __name__ == "__main__":     ret=target(1,2)     print 'The return value is %d' % ret E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py before function be called The parameter is 1,2 after function be called The return value is 3 Note:在装饰器中的内嵌包装函数的形参和返回值于原函数相同   2 被装饰器的参数不固定:当对装饰函数的参数个数不确定的时候,用*args,**kwargs是可以适配各种不同函数的方法。 def decorate(fun):     def inner(*args,**kwargs):         print 'before function be called %s' % fun.__name__         ret=fun(*args,**kwargs)         print 'after function be called %s' % fun.__name__         return ret     return inner @decoratedef target(a,b,c):     print 'The parameter is %s,%s,%s' % (a,b,c)     return a+b+c if __name__ == "__main__":     ret=target(1,2,3)     print 'The return value is %d' % ret E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py before function be called target The parameter is 1,2,3 after function be called target The return value is 6 3 装饰器带参数: def decorate(arg):     def inner(fun):         print arg         def deep_inner(a,b):             print 'before function be called %s' % fun.__name__             ret=fun(a,b)             print 'after function be called %s' % fun.__name__             return ret         return deep_inner     return inner @decorate('decorate')def target(a,b):     print 'the parameter is %s,%s' % (a,b)     return a+b if __name__ == "__main__":     ret=target(1,2)     print 'return value is %d' % ret E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py decorate before function be called target the parameter is 1,2 after function be called target return value is 3 装饰器带函数比之前的要复杂一点。但是其实我们写出装饰器的调用过程也就一目了然了。 对于不带参数的装饰器来说。函数可以写成target=decorate(target) 对于被装饰的函数如果携带参数,函数可以写成 target=decorate(target)(a,b) -> target=inner(a,b) 对于装饰器带参数来说。函数可以写成 target=decorate(arg)(target)(a,b) -> target=inner(target)(a,b) -> target=deep_inner(a,b) 将关系这样写出来是不是就明了很多了。在来看对应的代码 def decorate(arg)  这里接受装饰器的参数 def inner(fun) 接受被装饰的函数 def deep_inner(a,b) 接受被装饰函数的参数   4 class类的装饰器: class mydecorator(object):     def __init__(self,fn):         print 'inside mydecorator.__init__()'         self.fn=fn     def __call__(self):         self.fn()         print 'inside mydecorator.__call__()'@mydecoratordef function():     print 'inside function'if __name__ == "__main__":     function() E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py inside mydecorator.__init__() inside function inside mydecorator.__call__() 前面看到的都是以函数为装饰器,这里用到的是以类为装饰器。其实道理也很函数的一样 mydecorator(function) -> mydecorator.__init__(function) ->self.fn=functionfunction()的时候调用__call__.因此执行的顺序依次是__init__,function,__call__   5 class类装饰器带参数: class mydecorator(object):     def __init__(self,value):         print value     def __call__(self,func):         def wrapper(*args,**kwargs):             print 'inside __call__'             func(*args,**kwargs)         return wrapper @mydecorator('decorator test')def function():     print 'inside function'if __name__ == "__main__":     function() E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py decorator test inside __call__ inside function 在这里类mydecorator带了参数,因此不能在__init__中进行赋值。 但是在调用function的时候会用到__call__,因此在__call__里面对函数进行赋值。   闭包: 闭包的原理和装饰器是一个道理,在前面已经讲过。我们来讲讲闭包中的变量应用。先来看看局部变量以及全局变量。 global_para="outer para"def para_test():     local_para='inner para'     print 'locals:%s' % locals()     print global_para     print local_para locals:{'local_para': 'inner para'}   File "E:/py_prj/fluent_python/chapter7.py", line 77, in <module>     para_test()   File "E:/py_prj/fluent_python/chapter7.py", line 71, in para_test     print global_para UnboundLocalError: local variable 'global_para' referenced before assignment 这个代码报错。提示global_para还未赋值就被引用。原因是global_para是在函数外部定义的。因此是全局变量。而local_para是在内部定义的,因此是局部变量。那么在函数内部要使用外部变量,应该改成如下的形式: global_para="outer para"def para_test():     global global_para     global_para='changed to inner para'     local_para='inner para'     print 'locals:%s' % locals()     print global_para     print local_para print 'globals:%s' % globals() E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py locals:{'local_para': 'inner para'} changed to inner para inner para globals:{'function': <function wrapper at 0x017B44B0>, 'f1': <function f1 at 0x017B43B0>, 'wraps': <function wraps at 0x0174E8F0>, 'f3': <function f3 at 0x017B4430>, 'target': <function target at 0x0174E570>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'E:/py_prj/fluent_python/chapter7.py', 'register': <function register at 0x017B4370>, 'para_test': <function para_test at 0x017B4530>, 'f2': <function f2 at 0x017B43F0>, 'mydecorator': <class '__main__.mydecorator'>, '__author__': 'Administrator', 'global_para': 'changed to inner para', 'decorate': <function decorate at 0x0174E470>, 'time': <module 'time' (built-in)>, 'main': <function main at 0x017B4170>, '__name__': '__main__', '__package__': None, 'outerfunction': <function outerfunction at 0x017B42B0>, '__doc__': None, 'registry': [<function f1 at 0x017B43B0>, <function f2 at 0x017B43F0>]} 加上global关键字后,就相当于给这个变量进行了一个申明,因此会对全局变量进行引用。 其中locals()和globals()分别打印出局部变量以及全局变量。可以看到全局变量中所有在这个文件定义的函数都在里面。   我们再将代码修改下: global_para="outer para"def para_test():     global_para='changed to inner para'     local_para='inner para'     print 'locals:%s' % locals()     print global_para     print local_paraif __name__ == "__main__":     para_test()     print global_para E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py locals:{'global_para': 'changed to inner para', 'local_para': 'inner para'} changed to inner para inner para outer para 可以看到两次打印global_para结果都不一样,第一次是 changed to inner para”,第二次是 outer para’.尽管在函数中对global_para进行了赋值,但是这个值仍然被当做了局部变量,而非全局变量。 因此我们可以总结一下: 函数代码块外定义的是全局变量,在函数代码内部定义的是局部变量。   但这个和我们的闭包有什么关系呢。说到闭包,就要介绍下一个变量类型:自由变量。 def outer_function():     para='free para'     def innerfunc():         para='inner'         print 'the para is %s' % para         print 'inside innerfunc,locals %s' % locals()     print 'the para is %s' % para     print 'locals are %s' % locals()     return innerfunc 对于这个函数的执行,在innerfunc()中打印para应该是什么值? 是free para还是inner。在innerfunc()外部打印的又应该是什么值。来看下结果 E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py the para is inner inside innerfunc,locals {'para': 'inner'} the para is free para locals are {'innerfunc': <function innerfunc at 0x017D41F0>, 'para': 'free para'} 从打印来看。两次打印para分别是inner和free para。在innerfunc中试图修改para的值。但是发现修改只在innerfunc中生效。离开innerfunc后的值仍然是free para.我们将代码修改下: def outer_function():     para='free para'     def innerfunc():         para=para+'abc'         print 'inner para is %s' % para         print 'inside innerfunc,locals %s' % locals()     return innerfunc() E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter7.py Traceback (most recent call last):   File "E:/py_prj/fluent_python/chapter7.py", line 80, in <module>     outer_function()   File "E:/py_prj/fluent_python/chapter7.py", line 75, in outer_function     return innerfunc()   File "E:/py_prj/fluent_python/chapter7.py", line 72, in innerfunc     para=para+'abc' UnboundLocalError: local variable 'para' referenced before assignment UnboundLocalError: local variable 'para' referenced before assignment 居然报错了。原因是对para未赋值就引用了。这个para在outer_function内,innerfunc外定义的。那么到底应该算是局部变量还是外部变量呢,其实都不是,这个para就是自由变量。 我们来对自由变量做一个总结:如果一个参数在函数代码块内被定义,但是被这个函数代码块的其他函数引用,也就是闭包函数。那么这个参数就是自由变量。 在闭包函数中,会保留定义函数时存在的自由变量的绑定.但是在闭包函数对于数字,字符串,元组等不可变类型来说,只能读取,不能更新。如果尝试重新绑定,例如para=para+'abc', 就会隐式的创建局部变量para,那么para就不是自由变量了 那么在自由变量是否就完全不能修改了呢? 也不是在python2.7中,可以将自由变量赋值为一个列表,那么在闭包函数就可以修改这个自由变量的值 def outer_function():     para=[]     def innerfunc():         para.append('abc')         print 'inner para is %s' % para         print 'inside innerfunc,locals %s' % locals()     return innerfunc()python3中增加nonlocal的字段,在闭包函数中声明nonlocal para,也可以修改自由变量的值。

转载于:https://www.cnblogs.com/zhanghongfeng/p/7091588.html

相关资源:JAVA上百实例源码以及开源项目
最新回复(0)