zoukankan      html  css  js  c++  java
  • Python基础:13装饰器

    装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的应用有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

    1:装饰器实际就是函数。他们接受函数对象。对这些函数进行包装或修饰。因此,有了装饰器,可以在执行实际函数之前运行预备代码;也可以在执行实际函数代码之后做些清理工作。

    装饰器只是语法糖。有时这样做更方便,尤其是做元编程(在运行时改变程序的行为)时。

     

    2:装饰器的语法以@开头,接着是装饰器函数的名字和可选的参数。紧跟着定义被修饰的函数,所以,使用装饰器看起来会是这样:

    @decorator(dec_opt_args)
    def func2Bdecorated(func_opt_args):
        ...

     

    3:装饰器的本质

            在装饰器没有参数的情况下,如:

    @deco
    def foo():     pass 

    该定义等价于:   

    foo = deco(foo)

            在装饰器有参数的情况下,如:

    @deco(deco_args)
    def foo():     pass

    该定义等价于:   

    foo = deco(deco_args)(foo) 

     

            更复杂的,装饰器也可以“堆叠”,如:

    @deco1(deco1_args)
    @deco2
    def foo():     pass

    该定义等价于:

    foo = deco1(deco1_args)(deco2(foo))

    4:下面是不带参数的装饰器例子:

    def  deco(func):
           print 'this is deco'
           def wrapfun():
                  print 'in  wrapfun'
                  return func()
           return wrapfun
    
    
    @deco
    def  foo():
           print 'this is foo'
     
    print  'after def'
     
    foo()

    结果是:

    this is deco
    after def
    in wrapfun
    this is foo

            可见,在定义foo函数的时候,就会调用deco函数。下面是带参数的装饰器例子:

    def deco(args):
           print  'this is deco, args is ', args
           def  wrapfun1(func):
                  print  'in wrapfun1'
                  def  wrapfun2(fargs):
                         print  'in wrapfun2'
                         return  func(fargs)
                  return  wrapfun2
           return  wrapfun1
     
    @deco([1,2,3])
    def foo(fargs):
           print  'this is foo, fargs is ', fargs
     
    print 'after def'
     
    foo([4, 5, 6])

           结果是:

    this is deco, args is [1, 2, 3]
    in wrapfun1
    after def
    in wrapfun2
    this is foo, fargs is  [4, 5, 6]

    5:闭包。

     装饰器其实用到了闭包。在了解闭包之前,我们知道python中变量分为全局变量和局部变量。比如下面的f1和f2:

    def f1(a):
        print(a)
        print(b)

    执行f1时,肯定会报错:"NameError: global name 'b' is not defined"。这是因为在f1中没有给b赋值(初始化),因此f1认为b是个全局变量。这可以通过字节码看出来:

    >>> from dis import dis
    >>> dis(f1)
      2           0 LOAD_FAST                0 (a)
                  3 PRINT_ITEM          
                  4 PRINT_NEWLINE       
    
      3           5 LOAD_GLOBAL              0 (b)
                  8 PRINT_ITEM          
                  9 PRINT_NEWLINE       
                 10 LOAD_CONST               0 (None)
                 13 RETURN_VALUE        

        上面的2,3分别对应print a和print b。LOAD_FAST表示加载局部变量,LOAD_GLOBAL表示加载全局变量。

     

    下面是f2: 

    b = 6
    def f2(a):
        print(a)
        print(b)
        b = 9

     执行该函数时也会报错:UnboundLocalError: local variable 'b' referenced before assignment。

     这是因为Python编译函数的定义体时,它判断 b 是局部变量,因为在函数中给它赋值了。但是尝试获取局部变量 b 的值时,发现b没有绑定值。下面是f2的字节码:

    >>> dis(f2)
      2           0 LOAD_FAST                0 (a)
                  3 PRINT_ITEM          
                  4 PRINT_NEWLINE       
    
      3           5 LOAD_FAST                1 (b)
                  8 PRINT_ITEM          
                  9 PRINT_NEWLINE       
    
      4          10 LOAD_CONST               1 (9)
                 13 STORE_FAST               1 (b)
                 16 LOAD_CONST               0 (None)
                 19 RETURN_VALUE 

    闭包是指延伸了作用域的函数,闭包中包含函数定义体中引用、但是不在定义体中定义的非全局变量。 

    比如下面计算平均值的函数:

    def make_averager():
        series = []
        def averager(new_value):
            series.append(new_value)
            total = sum(series)
            return total/len(series)
    return average
    
    >>> avg = make_averager()
    >>> avg(10)
    10.0
    >>> avg(11)
    10.5
    >>> avg(12)
    11.0

    series 是 make_averager 函数的局部变量。可是,调用 avg(10) 时,make_averager 函数已经返回了,而它的本地作用域也一去不复返了。在 averager 函数中,series 是自由变量(free variable),自由变量指未在本地作用域中绑定的变量:

     

    审查返回的 averager 对象,我们发现 Python 在 __code__ 属性(表示编译后的函数定义体)中保存局部变量和自由变量的名称。avg 的 __closure__ 属性中,各个元素对应于 avg.__code__.co_freevars 中的一个名称。这些元素是 cell 对象,有个cell_contents 属性,保存着自由变量真正的值:

     

    >>> avg.__code__.co_varnames
    ('new_value', 'total')
    
    >>> avg.__code__.co_freevars
    ('series',)
    
    >>> avg.__closure__
    (<cell at 0x7f43624260f8: list object at 0x7f43624202d8>,)
    
    >>> avg.__closure__[0].cell_contents
    [10,11,12] 

    综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量。

     

    在Python 3 引入了 nonlocal 声明。它的作用是把变量标记为自由变量,如果为 nonlocal 声明的变量赋予新值,闭包中保存的绑定会更新。有了nonlocal后,可以设计效率更高的make_averager函数:

    def make_averager():
        count = 0
        total = 0
        def averager(new_value):
            nonlocal count, total
            count += 1
            total += new_value
            return total / count
        return averager 

    Python 2 没有 nonlocal,因此需要变通方法,处理方式是把内部函数需要修改的变量(如 count 和 total)存储为可变对象(如字典或简单的实例)的元素或属性,并且把那个对象绑定给一个自由变量。

     

    6:标准库中的装饰器

    Python 内置了三个用于装饰方法的函数:property、classmethod 和staticmethod。

     

    另一个常见的装饰器是 functools.wraps,它的作用是协助构建行为良好的装饰器。标准库中最值得关注的两个装饰器是 lru_cache (python3.2新增)和全新的singledispatch(Python 3.4 新增)。这两个装饰器都在 functools 模块中定义。接下来分别讨论它们。

    functools.lru_cache 是非常实用的装饰器,它实现了备忘(memoization)功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。生成第 n 个斐波纳契数这种慢速递归函数适合使用 lru_cache,

    import time
    
    def fibonacci(n):
            print('call fibonacci(%d)'%n)
            if n < 2:
                    return n
            return fibonacci(n-2) + fibonacci(n-1)
    
    if __name__ == '__main__':
            begin=time.time()
            fibonacci(6)
            end=time.time()
            print('run time is ', end-begin) 

    运行结果如下:

    call fibonacci(6)
    call fibonacci(4)
    call fibonacci(2)
    call fibonacci(0)
    call fibonacci(1)
    call fibonacci(3)
    call fibonacci(1)
    call fibonacci(2)
    call fibonacci(0)
    call fibonacci(1)
    call fibonacci(5)
    call fibonacci(3)
    call fibonacci(1)
    call fibonacci(2)
    call fibonacci(0)
    call fibonacci(1)
    call fibonacci(4)
    call fibonacci(2)
    call fibonacci(0)
    call fibonacci(1)
    call fibonacci(3)
    call fibonacci(1)
    call fibonacci(2)
    call fibonacci(0)
    call fibonacci(1)
    run time is  0.0003077983856201172 

    浪费时间的地方很明显:fibonacci(1) 调用了 8 次,fibonacci(2) 调用了 5 次……但是,如果增加两行代码,使用 lru_cache,性能会显著改善,

    import time
    import functools
    
    #@functools.lru_cache()
    def fibonacci(n):
            print('call fibonacci(%d)'%n)
            if n < 2:
                    return n
            return fibonacci(n-2) + fibonacci(n-1)
    
    if __name__ == '__main__':
            begin=time.time()
            fibonacci(6)
            end=time.time()
            print('run time is ', end-begin) 

    结果如下:

    call fibonacci(6)
    call fibonacci(4)
    call fibonacci(2)
    call fibonacci(0)
    call fibonacci(1)
    call fibonacci(3)
    call fibonacci(5)
    run time is  8.440017700195312e-05

     

     

    这样一来,执行时间减半了,而且 n 的每个值只调用一次函数。

      

    lru_cache 可以使用两个可选的参数来配置。它的签名是:

    functools.lru_cache(maxsize=128, typed=False)

     

     

    maxsize 参数指定存储多少个调用的结果。缓存满了之后,旧的结果会被扔掉,腾出空间。为了得到最佳性能,maxsize 应该设为 2 的幂。typed 参数如果设为 True,把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整数参数(如 1 和 1.0)区分开。顺便说一下,因为 lru_cache 使用字典存储结果,而且键根据调用时传入的定位参数和关键字参数创建,所以被 lru_cache 装饰的函数,它的所有参数都必须是可散列的。

     

    假设开发一个 Web 应用想生成 HTML,显示不同类型的 Python对象。我们可能会编写这样的函数:

    import html
    def htmlize(obj):
        content = html.escape(repr(obj))
        return '<pre>{}</pre>'.format(content) 

    这个函数适用于任何 Python 类型,但是现在我们想做个扩展,让它使用特别的方式显示某些类型。比如

    str:把内部的换行符替换为 '<br> ';不使用 <pre>,而是使用 <p>。

    int:以十进制和十六进制显示数字。

    list:输出一个 HTML 列表,根据各个元素的类型进行格式化。

     

    因为 Python 不支持重载方法或函数,所以我们不能使用不同的签名定义htmlize 的变体,也无法使用不同的方式处理不同的数据类型。在 Python 中,一种常见的做法是把 htmlize 变成一个分派函数,使用一串 if/elif/elif,调用专门的函数,如htmlize_str、htmlize_int,等等。这样不便于模块的用户扩展,还显得笨拙:时间一长,分派函数 htmlize 会变得很大,而且它与各个专门函数之间的耦合也很紧密。

    Python 3.4 新增的 functools.singledispatch 装饰器可以把整体方案拆分成多个模块,使用 @singledispatch 装饰的普通函数会变成泛函数(generic function):根据第一个参数的类型,以不同方式执行相同操作的一组函数。

    functools.singledispatch 是 Python 3.4 增加的,PyPI 中的singledispatch 包可以向后兼容 Python 2.6 到 Python 3.3。

    from functools import singledispatch
    from collections import abc
    import numbers
    import html
    
    @singledispatch
    def htmlize(obj):
        content = html.escape(repr(obj))
        return '<pre>{}</pre>'.format(content)
        
    @htmlize.register(str)
    def _(text):
        content = html.escape(text).replace('
    ', '<br>
    ')
        return '<p>{0}</p>'.format(content)
        
    @htmlize.register(numbers.Integral)
    def _(n):
        return '<pre>{0} (0x{0:x})</pre>'.format(n)
        
    @htmlize.register(tuple)
    @htmlize.register(abc.MutableSequence)
    def _(seq):
        inner = '</li>
    <li>'.join(htmlize(item) for item in seq)
        return '<ul>
    <li>' + inner + '</li>
    </ul>'
    
    
    if __name__ == '__main__':
        print(htmlize(abc))
        print(htmlize("hello, world"))
        print(htmlize(42))
        print(htmlize([1,2,3])) 

        运行结果如下:

    <pre>&lt;module &#x27;collections.abc&#x27; from &#x27;/usr/lib/python3.5/collections/abc.py&#x27;&gt;</pre>
    <p>hello, world</p>
    <pre>42 (0x2a)</pre>
    <ul>
    <li><pre>1 (0x1)</pre></li>
    <li><pre>2 (0x2)</pre></li>
    <li><pre>3 (0x3)</pre></li>
    </ul> 

    singledispatch 机制的一个显著特征是,你可以在系统的任何地方和任何模块中注册专门函数。如果后来在新的模块中定义了新的类型,可以轻松地添加一个新的专门函数来处理那个类型。此外,你还可以为不是自己编写的或者不能修改的类添加自定义函数。

     

    只有理解了装饰器的本质,才能写出没有错误的装饰器。可以在python langugae reference, python2.4 中“What’s New in Python 2.4”的文档以及PEP 318 中来阅读更多关于装饰器的内容。

     

    参考

    http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

     


  • 相关阅读:
    linux安装日志切割程序
    Linux下安装JDK
    深入理解Java注解类型(@Annotation)
    23种设计模式--代理模式-Proxy
    23种设计模式--建造者模式-Builder Pattern
    23种设计模式--工厂模式-Factory Pattern
    23种设计模式
    Java集合框架学习笔记
    MySQL入门笔记(一)
    二进制运算基础
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247202.html
Copyright © 2011-2022 走看看