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

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

  1. &lbrack;Python学习笔记&rsqb;&lbrack;第七章Python文件操作&rsqb;

    2016/1/30学习内容 第七章 Python文件操作 文本文件 文本文件存储的是常规字符串,通常每行以换行符'\n'结尾. 二进制文件 二进制文件把对象内容以字节串(bytes)进行存储,无法用笔 ...

  2. 流畅的python学习笔记:第九章:符合python风格的对象

    首先来看下对象的表现形式: class People():     def __init__(self,name,age):         self.name=name         self.a ...

  3. python学习笔记&lpar;5&rpar;--迭代器&comma;生成器&comma;装饰器&comma;常用模块&comma;序列化

    生成器 在Python中,一边循环一边计算的机制,称为生成器:generator. 如: >>> g = (x * x for xin range(10)) >>> ...

  4. python学习笔记-(八)装饰器、生成器&amp&semi;迭代器

    本节课程内容概览: 1.装饰器 2.列表生成式&迭代器&生成器 3.json&pickle数据序列化 1. 装饰器 1.1 定义: 本质上是个函数,功能是装饰其他函数—就是为其 ...

  5. Python学习日记(七)——装饰器

    1.必备知识 #### 一 #### def foo(): print 'foo' foo #表示是函数 foo() #表示执行foo函数 #### 二 #### def foo(): print ' ...

  6. 流畅的python学习笔记:第二章

    第二章开始介绍了列表这种数据结构,这个在python是经常用到的结构 列表的推导,将一个字符串编程一个列表,有下面的2种方法.其中第二种方法更简洁.可读性也比第一种要好 str='abc' strin ...

  7. 流畅的python学习笔记:第一章

    这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法.比如__len__, __getitem__. 并用一个纸牌的程序来讲解了这些方法 首先介绍下Tuple和nametup ...

  8. Python学习笔记(yield与装饰器)

    yeild:返回一个生成器对象: 装饰器:本身是一个函数,函数目的装饰其他函数(调用其他函数) 功能:增强被装饰函数的功能 装饰器一般接受一个函数对象作为参数,以便对其增强 @原函数名  来调用其他函 ...

  9. Python学习笔记(七)

    Python学习笔记(七): 深浅拷贝 Set-集合 函数 1. 深浅拷贝 1. 浅拷贝-多层嵌套只拷贝第一层 a = [[1,2],3,4] b = a.copy() print(b) # 结果:[ ...

随机推荐

  1. C&plus;&plus;基础知识易错点总结(1)

    1. 在C++中,不能被重载的运算符有: sizeof . 成员运算符 .* 成员指针运算符 :: 作用域运算符 ?: 条件运算符 2. C++语言多态性:编译时多态和运行时多态: 编译时多态可通过函 ...

  2. js自执行函数的几种不同写法的比较

    经常需要一个函数自执行,可惜这一种写法是错的: function(){alert(1);}();  原因是前半段“function(){alert(1);}”被当成了函数声明,而不是一个函数表达式,从 ...

  3. umbraco之DocumentType

    DocumentType定义了数据字段,这就像我们在数据库中定义表一样,这个数据字段就像表中的一个字段或者一个列.但不同的是,在umbraco里数据是分等级而不是一个表格性质. 这样就可以使用一个基本 ...

  4. 11&period;find 查找并复制文件

    请把系统上拥有者为ira用户的所有文件,并将其拷贝到/root/findfiles目录中 find /home/ira/ -user ira -exec cp -a {} /root/findfile ...

  5. 如何用jquery操作table的方法

    今天我在做你约我吧交友www.niyuewo.com网项目时遇到一个问题,就是如何用qjuery控制table的添加.编辑与删除,经过网上查资料发现用jquery很容易实现,在此整理下来供大家参考: ...

  6. 用高德地图API 通过详细地址获得经纬度

    http://cloud.sinyway.com/Service/amap.html http://restapi.amap.com/v3/geocode/geo?key=xxxxxxxxxxxxxx ...

  7. jQuery中的&dollar;&period;extend方法总结

    原文见:jQuery.extend()函数详解 Jquery的扩展方法extend是我们在写插件的过程中常用的方法,但是经常容易搞不清楚以下两个写法的关系: 1.$.extend(dest,src1, ...

  8. mybatis 详解(七)------一对一、一对多、多对多

    前面几篇博客我们用mybatis能对单表进行增删改查操作了,也能用动态SQL书写比较复杂的sql语句.但是在实际开发中,我们做项目不可能只是单表操作,往往会涉及到多张表之间的关联操作.那么我们如何用 ...

  9. 百度AI开放平台- API实战调用

    百度AI开放平台- API实战调用 一.      前言 首先说一下项目需求. 两个用户,分别上传了两段不同的文字,要计算两段文字相似度有多少,匹配数据库中的符合条件的数据,初步估计列出来会有60-1 ...

  10. OJ题&colon;字符串最后一个单词的长度

    题目描述 计算字符串最后一个单词的长度,单词以空格隔开. 输入描述: 一行字符串,非空,长度小于5000. 输出描述: 整数N,最后一个单词的长度. 输入例子: hello world 输出例子: 5 ...