理解Python的装饰器

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

Python的函数也是对象

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

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里函数的另外一个特性:函数可以定义在另外一个函数内(函数中定义函数)
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"

##

##

函数引用

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

  • 可以传递给变量
  • 可以在另一个函数中定义
    这意味着一个函数可以返回另外一个函数(返回值是一个函数),我们看一下例子:

    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…

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

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!
你应该已经能够理解装饰器了。装饰器就是一种包装,能够让你在不改变函数本身的情况下,在其之前或之后执行代码     ## 手工实现装饰器 你会如何实现一个装饰器呢?
# 一个装饰器是一个能够接收另外一个函数作为参数的函数
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
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!
  ## 揭开装饰器的面纱(好吧,我承认这个副标题略二) 之前的例子,我们通过使用装饰器语法来实现:
@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等同于:
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
  装饰器是 设计模式中装饰者模式的python实现。这些经典的设计模式都 被内嵌在python中来简化开发,就像iterators(迭代器)一样。 当然,你可以同时使用多个装饰器(手动装饰器实现):
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语法糖实现:
@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~
  接下来我们来看一些关于装饰器的高级用法 向被装饰的函数传递参数
# 这不是什么黑魔法,你只需像装饰器传递参数

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之后,你用相同的办法可以为方法创建装饰器,

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就行

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?
 

向装饰器传递参数

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

# 装饰器是个普通的函数

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调用它。这很重要,因为这个标志可以让你直接指出装饰器

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.

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

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.

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

@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.
你觉得怎么样,我们通过@来进行函数调用

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

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
像这样,一个需要参数的装饰器,参数可以以变量的形式传递进去:

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
 

 

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

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</pre>

然后可以这么使用:

# 你创造了了一个将作为装饰器使用的函数,然后向里面插入了一个装饰器

别忘了,插入的装饰器的标识符是 “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本身也是一个装饰器
    # 直接用装饰器会显得很杂乱
    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原则),举个例子:

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
 

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

@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