理解Python的装饰器

原文出自stackoverflow,英文好的同学可以直接戳这里,如果是人人看的那么可能排版不太好,可以戳这里

Python的函数也是对象

为了理解装饰器,你必须首先要理解python里的函数也是对象。这是一个很重要的概念,我们可以来看个小例子:

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
def shout(word="yes"):
return word.capitalize()+"!"

print shout()
# 输出 : 'Yes!'

# 作为一个对象,你可以像其他对象一样,将他赋给一个变量
scream = shout

# 注意我们没有使用shout() 因为我们不是在调用shout这个函数,
# 我们把shout这个函数传递给给scream
# 这意味着我们可以调用scream来代替调用shoot
print scream()
# 输出 : 'Yes!'

# 不仅如此,你删掉shout, 仍然可以调用scream
del shout
try:
print shout()
except NameError, e:
print e
#outputs: "name 'shout' is not defined"

print scream()
# 输出: 'Yes!'

我们先把上面的概念记下,过会我们会再回来。

Python里函数的另外一个特性:函数可以定义在另外一个函数内(函数中定义函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def talk():
# 你可以在函数“talk”中定义一个函数
def whisper(word="yes"):
return word.lower()+"..."

# 然后在“talk”函数里直接使用“whisper”
print whisper()

# 你调用了内部定义了“whisper”的talk函数,
# 每一次调用talk,talk中的whisper也会被调用
talk()
# 输出:
# "yes..."

# 但是在talk函数之外并不存在whisper:
try:
print whisper()
except NameError, e:
print e
# 输出 : "name 'whisper' is not defined”

函数引用

从上面的例子可以看出函数是对象,因此:

  • 可以传递给变量
  • 可以在另一个函数中定义
  • 这意味着一个函数可以返回另外一个函数(返回值是一个函数),我们看一下例子:
    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
    30
    31
    32
    33
    def getTalk(type="shout"):

    # 我们接着定义函数
    def shout(word="yes"):
    return word.capitalize()+"!"

    def whisper(word="yes") :
    return word.lower()+"...";

    # 然后返回定义的两个函数中的一个
    if type == "shout":
    # 我们不使用(),因为在这里我们不是在调用它
    # 返回一个函数对象
    return shout
    else:
    return whisper

    # 你怎么使用这个东西?

    # 得到一个函数对象然后传递给一个变量
    talk = getTalk()

    # 你可以看到这里的talk是一个函数对象
    print talk
    # 输出 : <function shout at 0xb7ea817c>

    # 这个对象是函数返回的一个对象
    print talk()
    # 输出 : Yes!

    # 你也可以直接用一种很难看的方式直接调用
    print getTalk("whisper")()
    # 输出 : yes…

既然你可以返回一个函数,那么你也可以传递一个函数对象作为参数

1
2
3
4
5
6
7
8
def doSomethingBefore(func): 
print "I do something before then I call the function you gave me"
print func()

doSomethingBefore(scream)
# 输出:
# I do something before then I call the function you gave me
# Yes!

你应该已经能够理解装饰器了。装饰器就是一种包装,能够让你在不改变函数本身的情况下,在其之前或之后执行代码

手工实现装饰器

你会如何实现一个装饰器呢?

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
30
31
32
33
34
35
36
37
38
39
# 一个装饰器是一个能够接收另外一个函数作为参数的函数
def my_shiny_new_decorator(a_function_to_decorate):

# 装饰器在内部定义了一个函数:the_wrapper_around_the_original_function
# 这是一个对原始函数(a_function_to_decorate)进行包装的函数
# 所以它可以在原始函数(a_function_to_decorate)之前或者之后执行一些代码
def the_wrapper_around_the_original_function():

# 在这里实现一些你希望在原函数执行前执行的代码
print "Before the function runs"

# 在这里调用原函数
a_function_to_decorate()

# 在这里实现一些需要在原函数执行后执行的代码
print "After the function runs"

# 在这时,a_function_todecorate还没有被执行(这是定义过程)
# 我们返回我们刚刚创建的装饰器函数:the_wrapper_around_the_original_function
# 这个装饰器包括了原函数以及我们在其之前和之后需要执行的代码
return the_wrapper_around_the_original_function

# 现在来考虑创建一个你不会再直接调用的函数
def a_stand_alone_function():
print "I am a stand alone function, don't you dare modify me"

a_stand_alone_function()
# 输出: I am a stand alone function, don't you dare modify me

# 现在通过装饰它来扩展它的行为
# 只需要将它传给装饰器,它就会动态的被装饰
# 然后你会得到一个新的可以使用的函数

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
# 输出:
# Before the function runs
# I am a stand alone function, don't you dare modify me
# After the function runs

现在,你每次想调用 a_stand_alone_function,都通过调用a_stand_alone_function_decorated来代替。

这个很好解决, 通过 my_shiny_new_decorator 返回的函数来重写 a_stand_alone_function_decorated

1
2
3
4
5
6
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#输出:
# Before the function runs
# I am a stand alone function, don't you dare modify me
# After the function runs# And guess what? That's EXACTLY what decorators do!

 

揭开装饰器的面纱(好吧,我承认这个副标题略二)

之前的例子,我们通过使用装饰器语法来实现:

1
2
3
4
5
6
7
8
9
@my_shiny_new_decorator
def another_stand_alone_function():
print "Leave me alone"

another_stand_alone_function()
#输出:
#Before the function runs
#Leave me alone
#After the function runs

就这没简单,

@decorator等同于:

1
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

 

装饰器是 设计模式中装饰者模式的python实现。这些经典的设计模式都 被内嵌在python中来简化开发,就像iterators(迭代器)一样。

当然,你可以同时使用多个装饰器(手动装饰器实现):

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
def bread(func):
def wrapper():
print "</''''''>"
func()
print "<______/>"
return wrapper

def ingredients(func):
def wrapper():
print "#tomatoes#"
func()
print "~salad~"
return wrapper

def sandwich(food="--ham--"):
print food

sandwich()
#输出: --ham--

sandwich = bread(ingredients(sandwich))
sandwich()
#输出:
#</''''''>
# #tomatoes#
# --ham--
# ~salad~
#<______/>

使用python语法糖实现:

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
@bread
@ingredients
def sandwich(food="--ham--"):
print food

sandwich()
#输出:
#</''''''>
# #tomatoes#
# --ham--
# ~salad~
#<______/>
The order you set the decorators MATTERS:

@ingredients
@bread
def strange_sandwich(food="--ham--"):
print food

strange_sandwich()
#输出:
##tomatoes#
#</''''''>
# --ham--
#<______/>
# ~salad~

 

接下来我们来看一些关于装饰器的高级用法

向被装饰的函数传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 这不是什么黑魔法,你只需像装饰器传递参数

def a_decorator_passing_arguments(function_to_decorate):
def a_wrapper_accepting_arguments(arg1, arg2):
print "I got args! Look:", arg1, arg2
function_to_decorate(arg1, arg2)
return a_wrapper_accepting_arguments

# 当你调用由装饰器返回的函数,
# 也就是你在调用a_wrapper_accepting_arguments这个函数,
# 把参数传递给wrapper然后再让它把参数传递给被装饰的原函数

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
print "My name is", first_name, last_name

print_full_name("Peter", "Venkman")
# 输出:
#I got args! Look: Peter Venkman
#My name is Peter Venkman

装饰对象的方法

在Python中对象和方法是很相似的,不同之处在于方法需要将当前对象的因作为第一个参数传递进去。这意味考虑到self之后,你用相同的办法可以为方法创建装饰器,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3 # very friendly, decrease age even more :-)
return method_to_decorate(self, lie)
return wrapper

class Lucy(object):

def __init__(self):
self.age = 32

@method_friendly_decorator
def sayYourAge(self, lie):
print "I am %s, what did you think?" % (self.age + lie)

l = Lucy()
l.sayYourAge(-3)
#输出: I am 26, what did you think?

当然,如果你想创建一个通用与函数(function)与方法(method)的装饰器,不需要考虑参数如何,只要用args和*kwargs就行

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
# 装饰器接收所有任何参数
def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
print "Do I have args?:"
print args
print kwargs
# 然后解压缩参数,在这是*args和**kwargs
# 如果你对压缩和解压缩参数不熟悉,
# 看这:http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
function_to_decorate(*args, **kwargs)
return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
print "Python is cool, no argument here."

function_with_no_argument()
#输出:
#Do I have args?:
#()
#{}
#Python is cool, no argument here.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
print a, b, c

function_with_arguments(1,2,3)
#输出:
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3

@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Why not ?"):
print "Do %s, %s and %s like platypus? %s" %
(a, b, c, platypus)

function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
#输出:
#Do I have args ? :
#('Bill', 'Linus', 'Steve')
#{'platypus': 'Indeed!'}
#Do Bill, Linus and Steve like platypus? Indeed!

class Mary(object):

def __init__(self):
self.age = 31

@a_decorator_passing_arbitrary_arguments
def sayYourAge(self, lie=-3): # You can now add a default value
print "I am %s, what did you think ?" % (self.age + lie)

m = Mary()
m.sayYourAge()
#输出:
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?

 

向装饰器传递参数

现在你可能想知道如何向装饰器本身传递参数。你可能不解为什么要这么做,因为之前的装饰器必须接受一个函数作为参数,但不能把被装饰的函数直接传递给装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 装饰器是个普通的函数
def my_decorator(func):
print "I am a ordinary function"
def wrapper():
print "I am function returned by the decorator"
func()
return wrapper

# 所以你可以直接调用而不不使用@

def lazy_function():
print "zzzzzzzz"

decorated_function = my_decorator(lazy_function)
# 输出: I am a ordinary function

# 它输出 "I am a ordinary function", 因为你调用了它

@my_decorator
def lazy_function():
print "zzzzzzzz"

# 输出: I am a ordinary function

my_decorator的调用和使用@my_decorator是十分相似的。你让通过被变量标志的my_decorator来让python调用它。这很重要,因为这个标志可以让你直接指出装饰器

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def decorator_maker():

print "I make decorators! I am executed only once: "+
"when you make me create a decorator."

def my_decorator(func):

print "I am a decorator! I am executed only when you decorate a function."

def wrapped():
print ("I am the wrapper around the decorated function. "
"I am called when you call the decorated function. "
"As the wrapper, I return the RESULT of the decorated function.")
return func()

print "As the decorator, I return the wrapped function."

return wrapped

print "As a decorator maker, I return a decorator"
return my_decorator

# 我们来创建一个装饰器.
new_decorator = decorator_maker()
# 输出:
# I make decorators! I am executed only once: when you make me create a decorator
# As a decorator maker, I return a decorator

# 然后我们来装饰一个函数
def decorated_function():
print "I am the decorated function."

decorated_function = new_decorator(decorated_function)
# outputs:
# I am a decorator! I am executed only when you decorate a function.
# As the decorator, I return the wrapped function

# 我们调用一下这个被装饰过的方法:
decorated_function()
#输出:
# I am the wrapper around the decorated function. I am called when you call the
# decorated function.
# As the wrapper, I return the RESULT of the decorated function.
# I am the decorated function.

再让我们跳过中间变量来实现同样的事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def decorated_function():
print "I am the decorated function."
decorated_function = decorator_maker()(decorated_function)
# 输出:
# I make decorators! I am executed only once: when you make me create a decorator
# As a decorator maker, I return a decorator
# I am a decorator! I am executed only when you decorate a function.
# As the decorator, I return the wrapped function.

# 最后:
decorated_function()
# 输出:
# I am the wrapper around the decorated function. I am called when you call the
# decorated function.
# As the wrapper, I return the RESULT of the decorated function.
# I am the decorated function.

让我们通过更简洁方式来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@decorator_maker()
def decorated_function():
print "I am the decorated function."
#输出:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

#然后就到最后了,调用:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

你觉得怎么样,我们通过@来进行函数调用

回到向装饰器传递参数这个问题上。如果我们使用函数来产生装饰器,我们也可以向这个函数传参

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
30
31
32
33
34
35
36
37
38
39
40
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

print "I make decorators! And I accept arguments:", decorator_arg1, decorator_arg2

def my_decorator(func):
# 向这传递参数是一种闭包特性
# 如果你不喜欢这种闭包特性,你可以把它传给一个变量
# 或者看看这:http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
print "I am the decorator. Somehow you passed me arguments:", decorator_arg1, decorator_arg2

# 不要弄混 装饰器的参数 和 被装饰的方法的参数
def wrapped(function_arg1, function_arg2) :
print ("I am the wrapper around the decorated function.n"
"I can access all the variablesn"
"t- from the decorator: {0} {1}n"
"t- from the function call: {2} {3}n"
"Then I can pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,
function_arg1, function_arg2))
return func(function_arg1, function_arg2)

return wrapped

return my_decorator

@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
print ("I am the decorated function and only knows about my arguments: {0}"
" {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments("Rajesh", "Howard")
#输出:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Sheldon
# - from the function call: Rajesh Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

像这样,一个需要参数的装饰器,参数可以以变量的形式传递进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
c1 = "Penny"
c2 = "Leslie"

@decorator_maker_with_arguments("Leonard", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
print ("I am the decorated function and only knows about my arguments:"
" {0} {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments(c2, "Howard")
#输出:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Penny
# - from the function call: Leslie Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Leslie Howard

 

 

做个练习:一个用来装饰装饰器的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def decorator_with_args(decorator_to_enhance):

# 我们像之前那样传递参数
def decorator_maker(*args, **kwargs):

# 创建一个只接受函数作为参数的函数
# 但是能够使用maker传递的参数
def decorator_wrapper(func):

# 我们将返回原始的装饰器,就像返回一个普通的函数一样
# IS JUST AN ORDINARY FUNCTION (which returns a function).
# 需要注意的是装饰器必须有自己的标识符,否则无法工作
return decorator_to_enhance(func, *args, **kwargs)

return decorator_wrapper

return decorator_maker

然后可以这么使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 你创造了了一个将作为装饰器使用的函数,然后向里面插入了一个装饰器
# 别忘了,插入的装饰器的标识符是 "decorator(func, *args, **kwargs)"
@decorator_with_args
def decorated_decorator(func, *args, **kwargs):
def wrapper(function_arg1, function_arg2):
print "Decorated with", args, kwargs
return func(function_arg1, function_arg2)
return wrapper

# 然后你可以通过被装饰的装饰器来装饰你的函数

@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
print "Hello", function_arg1, function_arg2

decorated_function("Universe and", "everything")
#输出:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything

# 酷不?

我知道,你上次有这种感觉,应该是听到别人说:“要理解递归,你要先理解递归”的时候。但是掌握了刚才讲的这些之后是不是很爽。

##

装饰器范例

  • 装饰器是Python2.4出现的新特性,确保你的代码跑在2.4或者更高版本上
  • 记住装饰器会延缓函数调用
  • 不能取消装饰。有一些方法能创建可以取消的装饰器,但木有人用。一个函数被装饰了,就是被装饰了。
  • 装饰器装饰函数后,会增加debug难度
    Python2.5通过提供functool模块来解决了上述的最后一个问题。functool.wraps会拷贝被装饰函数的名字 模块 文档等到他的包装器中,而functools.wraps本身也是一个装饰器
    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
    30
    31
    32
    33
    # 直接用装饰器会显得很杂乱
    def bar(func):
    def wrapper():
    print "bar"
    return func()
    return wrapper

    @bar
    def foo():
    print "foo"

    print foo.__name__
    #outputs: wrapper

    # "functools" 有助于解决这个问题

    import functools

    def bar(func):
    # 我们说的包装器(wrapper)是用来包装func的
    # 下面是见证奇迹的时刻
    @functools.wraps(func)
    def wrapper():
    print "bar"
    return func()
    return wrapper

    @bar
    def foo():
    print "foo"

    print foo.__name__
    #输出: foo

 

如何有效的使用装饰器

也许你会有个疑问:我可以用装饰器做什么,它看起来很好很强大,但是如果有个实际例子会更好。

关于在什么情况下使用有很多种可能,传统的用法是当你需要扩展一个外部库函数的行为或者处于debug的需要(但你不想对其直接做做更改,因为这只是临时的),你可以使用一个装饰器来扩展许多函数,不要重写每个函数(DRY原则),举个例子:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def benchmark(func):
"""
A decorator that prints the time a function takes
to execute.
"""
import time
def wrapper(*args, **kwargs):
t = time.clock()
res = func(*args, **kwargs)
print func.__name__, time.clock()-t
return res
return wrapper

def logging(func):
"""
A decorator that logs the activity of the script.
(it actually just prints it, but it could be logging!)
"""
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print func.__name__, args, kwargs
return res
return wrapper

def counter(func):
"""
A decorator that counts and prints the number of times a function has been executed
"""
def wrapper(*args, **kwargs):
wrapper.count = wrapper.count + 1
res = func(*args, **kwargs)
print "{0} has been used: {1}x".format(func.__name__, wrapper.count)
return res
wrapper.count = 0
return wrapper

@counter
@benchmark
@logging
def reverse_string(string):
return str(reversed(string))

print reverse_string("Able was I ere I saw Elba")
print reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!")

#输出:
#reverse_string ('Able was I ere I saw Elba',) {}
#wrapper 0.0
#wrapper has been used: 1x
#ablE was I ere I saw elbA
#reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

 

而使用装饰器的好处是是不需要做多余的重写就能立刻使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@counter
@benchmark
@logging
def get_random_futurama_quote():
import httplib
conn = httplib.HTTPConnection("slashdot.org:80")
conn.request("HEAD", "/index.html")
for key, value in conn.getresponse().getheaders():
if key.startswith("x-b") or key.startswith("x-f"):
return value
return "No, I'm ... doesn't!"

print get_random_futurama_quote()
print get_random_futurama_quote()

#输出:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!

 

 

Python本身提供了许多装饰器:property, staticmethod等等。Django使用装饰器来捕获和查看权限等,装饰器用途广泛

 

 

原文出自:http://stackoverflow.com/questions/739ddd654/how-can-i-make-a-chain-of-function-decorators-in-python/1594484#1594484