zoukankan      html  css  js  c++  java
  • 流畅的python学习笔记第七章:装饰器

    装饰器就如名字一样,对某样事物进行装饰过后然后返回一个新的事物。就好比一个毛坯房,经过装修后,变成了精装房,但是房子还是同样的房子,但是模样变了。
    我们首先来看一个函数。加入我要求出函数的运行时间。一般来说代码写成如下。但是我有很多个函数都要计算运行时间。每个函数里面都要写一个计时的过程是一件很麻烦的事。
    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-start

    if __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

    @decorate
    def target():
        print 'running target()'
    回到我们刚才的疑问。有100个target函数需要计算运行时间。有没有简洁的调用方法。有了装饰器,我们的代码就可以简化成
    @decorate
    def target1():
        print 'running target()'
    .
    .
    .
    @decorate
    def 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

    @decorate
    def target():
        print 'running target()'

    if
    __name__ == "__main__":
        target()
        print target
    E:python2.7.11python.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

    @decorate
    def target():
        print 'running target()'
    if
    __name__ == "__main__":
        target()
    decorate中新增了一条打印语句:print 'decoreate working'
    E:python2.7.11python.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

    @register
    def f1():
        print 'running f1()'

    @register
    def 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.11python.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

    @decorate
    def 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.11python.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

    @decorate
    def 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.11python.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.11python.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__()'

    @mydecorator
    def function():
        print 'inside function'

    if
    __name__ == "__main__":
        function()
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter7.py
    inside mydecorator.__init__()
    inside function
    inside mydecorator.__call__()
    前面看到的都是以函数为装饰器,这里用到的是以类为装饰器。其实道理也很函数的一样
    mydecorator(function) -> mydecorator.__init__(function) ->self.fn=function
    function()的时候调用__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.11python.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.11python.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_para

    if __name__ == "__main__":
        para_test()
        print global_para
    E:python2.7.11python.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.11python.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.11python.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,也可以修改自由变量的值。
  • 相关阅读:
    Linux常用命令_(系统设置)
    Linux常用命令_(系统管理)
    Linux常用命令_(基本命令)
    敏捷测试的流程
    Web测试Selenium:如何选取元素
    Selenium学习
    Selenium介绍
    Selenium测试规划
    HTTPS传输协议原理
    常见的加密算法
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/7091588.html
Copyright © 2011-2022 走看看