zoukankan      html  css  js  c++  java
  • Python装饰器详解

    转载请注明来源:http://blog.csdn.net/TangHuanan/article/details/45094497

    返璞归真, 看山还是山

    刚看到Python装饰器时, 觉得很神奇。简单实验下,发现也就那么回事。但是慢慢的看到越来越多的装饰器。很多时候又不了解到底是怎么回事了。

    最后还是决定好好研究下。

    先看看一些实例, 然后再来分析下原理 
    假设我们有如下的基本函数

    def do_something():
        for i in range(1000000):
            pass
        print "play game"
    
    do_something()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结果如下:

    play game
    • 1

    需求1: 统计函数的执行时间

    1. 不是装饰器的装饰器

    import time
    def decorator(fun):
        start = time.time()
        fun()
        runtime = time.time()-start
        print runtime
    
    def do_something():
        for i in range(1000000):
            pass
        print "play game"
    
    decorator(do_something)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果如下:

    play game
    0.0299999713898
    • 1
    • 2

    这种实现看上去还可以,但是每次调用的是decorator,还要把函数作为一个参数传入。这样需要修改调用的地方,使用起来就不方便了。

    2. 最简单的装饰器

    import time
    def decorator(fun):
        def wrapper():
            start = time.time()
            fun()
            runtime = time.time()-start
            print runtime
        return wrapper
    @decorator
    def do_something():
        for i in range(1000000):
            pass
        print "play game"
    
    do_something()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果如下:

    play game
    0.0329999923706
    • 1
    • 2

    装饰器是在函数定义时前面加@,然后跟装饰器的实现函数。可以看出,现在只要直接调用do_something就可以了。调用的地方不要作任何修改。

    3. 目标函数带固定参数的装饰器

    import time
    def decorator(fun):
        def wrapper(name):
            start = time.time()
            fun(name)
            runtime = time.time()-start
            print runtime
        return wrapper
    @decorator
    def do_something(name):
        for i in range(1000000):
            pass
        print "play game " + name
    
    do_something("san guo sha")
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果如下:

    play game san guo sha
    0.039999961853
    • 1
    • 2

    实现很简单, 就是给wrapper函数参加相同的参数

    4. 目标函数带不固定参数的装饰器

    import time
    def decorator(fun):
        def wrapper(*args, **kwargs):
            start = time.time()
            fun(*args, **kwargs)
            runtime = time.time()-start
            print runtime
        return wrapper
    @decorator
    def do_something(name):
        for i in range(1000000):
            pass
        print "play game " + name
    
    @decorator
    def do_something2(user, name):
        for i in range(1000000):
            pass
        print user+" play game " + name
    
    do_something("san guo sha")
    do_something2("wang xiao er","san guo sha")
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    结果如下:

    play game san guo sha
    0.029000043869
    wang xiao er play game san guo sha
    0.0310001373291
    • 1
    • 2
    • 3
    • 4

    需求2: 目标函数每次调用重复执行指定的次数

    5. 让装饰器带参数

    import time
    def decorator(max):
        def _decorator(fun):
            def wrapper(*args, **kwargs):
                start = time.time()
                for i in xrange(max):
                    fun(*args, **kwargs)
                runtime = time.time()-start
                print runtime
            return wrapper
        return _decorator
    @decorator(2)
    def do_something(name):
        for i in range(1000000):
            pass
        print "play game " + name
    
    do_something("san guo sha")
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    结果如下:

    play game san guo sha
    play game san guo sha
    0.0600001811981
    • 1
    • 2
    • 3

    6. 原理

    看了这么多实例, 装饰器的基本类型也基本上都有了。是不是清楚了呢? 
    如果还是不清楚,那就继续看下面的内容。

    1 不带参数的装饰器

    @a_decorator
    def f(...):
        ...
    
    #经过a_decorator后, 函数f就相当于以f为参数调用a_decorator返回结果。
    f = a_decorator(f)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    来分析这个式子, 可以看出至少要满足以下几个条件 
    1. 装饰器函数运行在函数定义的时候 
    2. 装饰器需要返回一个可执行的对象 
    3. 装饰器返回的可执行对象要兼容函数f的参数

    2 验证分析

    1 装饰器运行时间

    import time
    def decorator(fun):
        print "decorator"
        def wrapper():
            print "wrapper"
            start = time.time()
            fun()
            runtime = time.time()-start
            print runtime
        return wrapper
    @decorator
    def do_something():
        for i in range(1000000):
            pass
        print "play game"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    结果如下:

    decorator
    • 1

    可以看出, 这里的do_something并没有调用, 但是却打印了decorator, 可wrapper没有打印出来。也就是说decorator是在do_something调用的时候执行的。

    2 返回可执行的对象

    import time
    def decorator(fun):
        print "decorator"
        def wrapper():
            print "wrapper"
            start = time.time()
            fun()
            runtime = time.time()-start
            print runtime
        return None
    @decorator
    def do_something():
        for i in range(1000000):
            pass
        print "play game"
    
    do_something()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    结果如下:

    decoratorTraceback (most recent call last):
      File "deco.py", line 17, in <module>
        do_something()
    TypeError: 'NoneType' object is not callable
    • 1
    • 2
    • 3
    • 4

    3 兼容函数f的参数

    import time
    def decorator(fun):
        print "decorator"
        def wrapper():
            print "wrapper"
            start = time.time()
            fun()
            runtime = time.time()-start
            print runtime
        return wrapper
    @decorator
    def do_something(name):
        for i in range(1000000):
            pass
        print "play game"
    
    do_something("san guo sha")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    结果如下:

    decoratorTraceback (most recent call last):
      File "deco.py", line 17, in <module>
        do_something("san guo sha")
    TypeError: wrapper() takes no arguments (1 given)
    • 1
    • 2
    • 3
    • 4

    看到这里, 至少对不带参数的装饰器应该全弄清楚了, 也就是说能到看山还是山了。

    3 带参数的装饰器

    这里就给一个式子, 剩下的问题可以自己去想

    @decomaker(argA, argB, ...)
    def func(arg1, arg2, ...):
        pass
    #这个式子相当于
    func = decomaker(argA, argB, ...)(func)
    • 1
    • 2
    • 3
    • 4
    • 5

    4 被装饰过的函数的函数名

    import time
    def decorator(fun):
        def wrapper():
            start = time.time()
            fun()
            runtime = time.time()-start
            print runtime
        return wrapper
    @decorator
    def do_something():
        print "play game"
    
    print do_something.__name__
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果如下:

    wrapper
    • 1

    可以看出, do_something的函数名变成了wrapper,这不是我们想要的。原因估计各位也都清楚了。那要怎么去解决呢?

    import time
    def decorator(fun):
        def wrapper():
            start = time.time()
            fun()
            runtime = time.time()-start
            print runtime
        wrapper.__name__ = fun.__name__
        return wrapper
    @decorator
    def do_something():
        print "play game"
    
    print do_something.__name__
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    结果如下:

    do_something
    • 1

    但是这个看起来是不是很不专业, python的unctools.wraps提供了解决方法

    import time
    import functools 
    def decorator(fun):
        @functools.wraps(fun)
        def wrapper():
            start = time.time()
            fun()
            runtime = time.time()-start
            print runtime
        return wrapper
    @decorator
    def do_something():
        print "play game"
    
    print do_something.__name__
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果如下:

    do_something
    • 1

    到此为止, 你是不是觉得已经完全明白了呢? 
    但事实是, 这其实还不够

    7. 装饰器类

    需求3: 让函数只能运行指定的次数 
    前面我们讲的都是函数式的装饰器, 那么类能不能成为装饰器呢?

    import time
    import functools 
    
    class decorator(object):
        def __init__(self, max):
            self.max = max
            self.count = 0
        def __call__(self, fun):
            self.fun = fun
            return self.call_fun
    
        def call_fun(self, *args, **kwargs):
            self.count += 1
            if ( self.count == self.max):
                print "%s run more than %d times"%(self.fun.__name__, self.max)
            elif (self.count<self.max):
                self.fun(*args, **kwargs)
            else:
                pass
    
    @decorator(10)
    def do_something():
        print "play game"
    @decorator(15)
    def do_something1():
        print "play game 1"
    for i in xrange(20):
        do_something()
        do_something1()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    结果如下:

    play game
    play game 1
    play game
    play game 1
    play game
    play game 1
    play game
    play game 1
    play game
    play game 1
    play game
    play game 1
    play game
    play game 1
    play game
    play game 1
    play game
    play game 1
    do_something run more than 10 times
    play game 1
    play game 1
    play game 1
    play game 1
    play game 1
    do_something1 run more than 15 times
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    是不是感觉有点怪, 但它确实是可行的。 
    在Python中, 其实函数也是对象。 反过来, 对象其实也可以像函数一样调用, 只要在类的方法中实现__call__方法。回想一下创建对象的过程

    class A:
        def __init__(self):
            pass
    a = A()
    • 1
    • 2
    • 3
    • 4

    这其实和函数调用没什么区别, 那么把这个式子代入到之前两个装饰器的式子中,结果如下: 
    带参数的装饰器 
    fun = A.__init__(args)(fun) 
    不带参数的装饰器 
    fun = A.__init__(fun)()

    现在装饰器的内容基本差不多了。 还有一些问题, 可以自己去尝试研究。

    自己的才是自己的

    还有几个问题如下: 
    1. 类装饰器(装饰器装饰的对象是类) 
    2. 类函数装饰器(装饰器装饰的对象是类的函数) 
    3. 多个装饰器一起使用(函数嵌套)

    8 参考资料

    https://www.python.org/dev/peps/pep-0318/ 
    https://docs.python.org/2/library/functools.html

    没有借口
  • 相关阅读:
    Markdown语法
    Hello World
    sql笔试题-1
    解决高版本vm打开虚拟机报错
    zookeeper启动闪退
    java找出1~1000中素数的三种方式
    Java中更精确的计时
    vue系列之调试工具(vue-devtools)
    vue系列之npm命令错误
    vue系列之安装基础环境
  • 原文地址:https://www.cnblogs.com/wutao1935/p/8472960.html
Copyright © 2011-2022 走看看