原文作者: e-satis
原文连接: stackoverflow - How can I make a chain of function decorators in Python
译者: kissg(赵喧典)
为了理解装饰器, 你必须先理解: 在Python的世界, 函数是对象. 这一点很重要. 让我们通过一个简单的例子来看看为什么:
def shout(word="yes"): return word.capitalize()+"!" print shout() # 输出 : 'Yes!' # 作为一个对象, 你可以像任何其他对象一样, 将函数赋值给一个变量 scream = shout # 注意, 我们并没有使用括号: # 我们没有调用函数, 我们是将"shout"函数装入到"scream"变量中 # 这意味着你可以像从"scream"调用"shout" print scream() # 输出 : 'Yes!' # 不仅如此, 它还意味着你可以删除旧的名字"shout", # 而仍然可以通过"scream"访问函数 del shout try: print shout() except NameError, e: print e # 输出: "name 'shout' is not defined" print scream() # 输出: 'Yes!'
Okay! 在脑海中保留这个概念. 我们之后会用到它.
Python函数的另一个有趣的属性是: 它们可以在另一个函数的内部被定义…
def talk(): # 你可以在运行时, 在"talk"函数内部定义一个函数 def whisper(word="yes"): return word.lower()+"..." # ... 然后立马使用它 print whisper() # 当调用"talk"时, 你的每一次调用都会定义"whisper"一次, # 再调用"whisper" talk() # 输出: # "yes..." # 但是, "whisper"在"talk"函数之外实际是不存在的 try: print whisper() except NameError, e: print e #输出 : "name 'whisper' is not defined" #Python的函数是对象
还跟得上吗? 现在开始讲有趣的部分…
你已经见识过了函数是对象. 因此,
这意味着 函数可以返回( return
)另一个函数 . 看仔细了!
def getTalk(kind="shout"): # 我们在运行时定义函数 def shout(word="yes"): return word.capitalize()+"!" def whisper(word="yes") : return word.lower()+"..."; # 然后我们返回其中的一个 if kind == "shout": # 不适用括号, 我们并不是在调用函数 # 我们是在返回一个函数对象 return shout else: return whisper # 你要如何使用这洪荒巨兽呢? # 取得函数, 并将其赋给一个变量 talk = getTalk() # 此处, 你可以看见"talk"是一个函数对象 print talk #输出 : <function shout at 0xb7ea817c> # 以下对象是函数"talk"的一项返回 print talk() #输出 : Yes! # 你甚至可以直接使用 # And you can even use it directly if you feel wild: print getTalk("whisper")() #输出 : yes...
等等…不止如此!
如果你要 return
一个函数, 可以传入一个函数作为参数:
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!
现在, 你已经具备了理解装饰器所需的一切知识. 如你所见, 装饰器就是”包装纸(wrapper)”, 也就是说, 装饰器允许你在被装饰的函数前后执行代码 , 而不对函数本身做任何修改.
你最好动手写一写(装饰器):
# 装饰器是函数, 它以另一个函数作为参数 def my_shiny_new_decorator(a_function_to_decorate): # 在装饰器的内部, 定义一个"wrapper"函数 # wrapper函数用于包装原函数, 以在原函数前后执行代码 def the_wrapper_around_the_original_function(): # 在此处输入你想在调用原函数之前执行的代码 print "Before the function runs" # 调用被装饰的函数(是函数调用, 需要括号) a_function_to_decorate() # 在此处输入你想在调用原函数之后执行的代码 print "After the function runs" # 此处, 被装饰的函数并没有被执行 # 返回刚刚创建的包装函数 # 包装函数包括被装饰的函数, 在函数前后执行的代码 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
即可.
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 # 这就是装饰器的作用了
用装饰器语法重现先前的例子, 是这样的:
@my_shiny_new_decorator def another_stand_alone_function(: print "Leave me alone" another_stand_alone_function( #outputs: #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中有一些经典的设计模式, 可使开发更加容易(比如迭代器(iterator)).
当然, 你可以叠加装饰器:
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~ #</______/>
你设置装饰器的顺序很重要:
@ingredients @bread def strange_sandwich(food="--ham--"): print food strange_sandwich() #输出: ##tomatoes# #</''''''/> # --ham-- #</______/> # ~salad~
(译者注: 本文是stackoverflow上一个回答)
结论是, 如你所见, 很容易就可以实现串级装饰器:
# 加粗装饰器 def makebold(fn): # 装饰器返回的新的函数 def wrapper(): # 在前后插入一些代码 return "<b>" + fn() + "</b>" return wrapper # 斜体装饰器 def makeitalic(fn): # 装饰器返回的新的函数 def wrapper(): # 在前后插入一些代码 return "<i>" + fn() + "</i>" return wrapper @makebold @makeitalic def say(): return "hello" print say() #输出: <b><i>hello</i></b> # 等价于 def say(): return "hello" say = makebold(makeitalic(say)) print say() #输出: <b><i>hello</i></b>
现在, 你可以愉快地离开了(译者注: 这是作者对提问者说的). 或者接着烧脑, 看看装饰器的一些高级用法.
# 这并不是黑魔法, 你只需要在包装过程中传入参数 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 # 当你调用装饰器返回的函数时, 你调用了包装纸(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好的一点是, 方法(method)和函数(function)实际是一样的. 唯一的区别在于, 方法的第一个参数需要是当前对象( self
)的引用.
这意味着, 你可以用同样的方式创建方法的装饰器! 不过, 别忘了 self
.
def method_friendly_decorator(method_to_decorate): def wrapper(self, lie): lie = lie - 3 # 真好, 年龄更小了 :-) 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?
如果要写一个通用的装饰器–可用于任何函数或方法, 而不必考虑其参数–那么, 用 *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 # 将得到的参数解压(unpack), 此处为*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就是这么酷, 此地无参. @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!'} #Bill, Linus and Steve喜欢鸭嘴兽吗? 千真万确! class Mary(object): def __init__(self): self.age = 31 @a_decorator_passing_arbitrary_arguments def sayYourAge(self, lie=-3): # 现在你可以添加一个默认值 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 an 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 an ordinary function # 它将会输出 "I am an ordinary function", 因为这就是你所做的: # 调用一个函. 没有一点魔法在里面 @my_decorator def lazy_function(): print "zzzzzzzz" #输出: I am an ordinary function
上述两者完全相同, 都是调用了 my_decorator
. 所以当你 @my_decorator
, 你是在告诉Python去调用被变量 my_decorator
标记的函数.
这一点很重要! 你可以用标签直接指明装饰器, 或者不这样.
让我们再深入一点.
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) #输出: #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." #outputs: #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. #Eventually: 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): # 此处传递参数的能力, 得益于闭包的性质 # 如果你不习惯闭包, 你可以假设它是ok的, 或者看一下这篇文章: # 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 variables/n" "/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
如你所见, 使用该技巧, 你可以像任何(普通)函数一样向装饰器传递参数. 如果你愿意, 你甚至可以使用 *args, **kwargs
. 但是, 请记住装饰器只能被调用 1次 , 即在导入脚本的时候. 之后你就不能动态地设置参数了. 当你”import x”时, 函数已经被装饰了 , 你不能再做任何修改.
作为福利, 我将给你一个能接收任意参数的装饰器代码片段. 然后, 我们用另一个函数来创建我们的装饰器, 以接收参数.
我们对装饰器进行了包装.
如前所见, 我们是用什么包装了函数?
是的, 就是装饰器!
让我们愉快地写一个装饰器的装饰器吧:
def decorator_with_args(decorator_to_enhance): """ 这个函数被用作一个装饰器. 它必须装饰另一个函数, 那个函数也是一个装饰器 它允许任何装饰器接收任意数量的参数, 这样就不用每次写(装饰器)的时候都回忆一遍. """ # 使用了与上述一样的技巧传递参数 def decorator_maker(*args, **kwargs): # 我们在运行时创建了一个装饰器, 它接收一个函数作为参数 # 但它可以持有maker传来的参数 def decorator_wrapper(func): # 我们返回原始装饰器的结果, 一个普通函数 # 唯一的异错的地方在于: 装饰器必须这样才能正常工作 return decorator_to_enhance(func, *args, **kwargs) return decorator_wrapper return decorator_maker
它可以像下述一样使用:
# 你可以创建一个用作装饰器的函数. 再用一个装饰器来装饰它 # 不要忘了, 用法是"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.5引入了 functools
模块, 该模块包含的 funtools.wraps()
函数, 可以将被装饰函数名称, 模块, 文档字符串(docstring)拷贝到它的包装纸中.
(有趣的事实是: funtools.wraps()
还是一个装饰器!)
# 为了调试方便, 栈回溯(stacktrace)打印函数的__name__属性 def foo(): print "foo" print foo.__name__ #输出: foo # 当使用装饰器时, 就显得混乱了 def bar(func): def wrapper(): print "bar" return func() return wrapper @bar def foo(): print "foo" print foo.__name__ #输出: 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
一个大问题: 我能用装饰器做什么?
(装饰器)看上去很酷很强大, 但是用一个实际例子来说明可能会更好. 装饰器的用法很多. 经典的用法是, 拓展一个外部库函数(你无法修改它)的功能, 或者用于调试(因为它是临时的, 你并不想进行修改).
你可以用装饰器不重复地拓展多个函数, 像这样:
def benchmark(func): """ 打印函数被执行的时间的装饰器 """ 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): """ 记录脚本活动的装饰器 (事实上, 它只是进行了打印, 但可以进行日志打印) """ def wrapper(*args, **kwargs): res = func(*args, **kwargs) print func.__name__, args, kwargs return res return wrapper def counter(func): """ 统计并打印函数被执行次数的装饰器 """ 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
当然, 装饰器的好处是, 对于几乎所有东西, 在不重写的情况, 马上可用. 我说过, 不重复(D.R.Y, Don’t Repeat Yourself):
@counter @benchmark @logging def get_random_futurama_quote(): from urllib import urlopen result = urlopen("http://subfusion.net/cgi-bin/quote.pl?quote=futurama").read() try: value = result.split("<br><b><hr><br>")[1].split("<br><br><hr>")[0] return value.strip() except: 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
等等.
装饰器确实大有用途.