首页 > 代码库 > 流畅的python学习笔记第七章:装饰器

流畅的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.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

@decorate
def 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

@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.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

@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.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

@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.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__()‘

@mydecorator
def 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=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.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_para

if __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,也可以修改自由变量的值。

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