wutao1935

导航

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

posted on 2018-02-26 14:17  逐风浪子  阅读(699)  评论(0编辑  收藏  举报