如何做一个function装饰链?

我怎样才能在Python中做两个装饰器,可以做到以下几点?

@makebold @makeitalic def say(): return "Hello" 

…应该返回:

 "<b><i>Hello</i></b>" 

我不是试图在真正的应用程序中这样做HTML – 只是想了解装饰器和装饰器链接如何工作。

查看文档以查看装饰器是如何工作的。 这是你要求的:

 def makebold(fn): def wrapped(): return "<b>" + fn() + "</b>" return wrapped def makeitalic(fn): def wrapped(): return "<i>" + fn() + "</i>" return wrapped @makebold @makeitalic def hello(): return "hello world" print hello() ## returns "<b><i>hello world</i></b>" 

如果没有长时间的解释,请参阅Paolo Bergantino的回答 。

装饰者基础

Python的function是对象

为了理解装饰器,你必须首先理解函数是Python中的对象。 这有重要的后果。 让我们来看一个简单的例子:

 def shout(word="yes"): return word.capitalize()+"!" print(shout()) # outputs : 'Yes!' # As an object, you can assign the function to a variable like any other object scream = shout # Notice we don't use parentheses: we are not calling the function, # we are putting the function "shout" into the variable "scream". # It means you can then call "shout" from "scream": print(scream()) # outputs : 'Yes!' # More than that, it means you can remove the old name 'shout', # and the function will still be accessible from 'scream' del shout try: print(shout()) except NameError, e: print(e) #outputs: "name 'shout' is not defined" print(scream()) # outputs: 'Yes!' 

记住这一点。 我们马上会回过头来。

Python函数的另一个有趣的属性是它们可以在另一个函数中定义!

 def talk(): # You can define a function on the fly in "talk" ... def whisper(word="yes"): return word.lower()+"..." # ... and use it right away! print(whisper()) # You call "talk", that defines "whisper" EVERY TIME you call it, then # "whisper" is called in "talk". talk() # outputs: # "yes..." # But "whisper" DOES NOT EXIST outside "talk": try: print(whisper()) except NameError, e: print(e) #outputs : "name 'whisper' is not defined"* #Python's functions are objects 

函数引用

好的,还在吗? 现在有趣的部分…

你已经看到函数是对象。 因此,function:

  • 可以分配给一个variables
  • 可以在另一个函数中定义

这意味着一个函数可以return另一个函数

 def getTalk(kind="shout"): # We define functions on the fly def shout(word="yes"): return word.capitalize()+"!" def whisper(word="yes") : return word.lower()+"..."; # Then we return one of them if kind == "shout": # We don't use "()", we are not calling the function, # we are returning the function object return shout else: return whisper # How do you use this strange beast? # Get the function and assign it to a variable talk = getTalk() # You can see that "talk" is here a function object: print(talk) #outputs : <function shout at 0xb7ea817c> # The object is the one returned by the function: print(talk()) #outputs : Yes! # And you can even use it directly if you feel wild: print(getTalk("whisper")()) #outputs : yes... 

还有更多!

如果你可以return一个函数,你可以传递一个参数:

 def doSomethingBefore(func): print("I do something before then I call the function you gave me") print(func()) doSomethingBefore(scream) #outputs: #I do something before then I call the function you gave me #Yes! 

那么,你只需要了解装饰器所需的一切。 你看,装饰器是“包装器(wrappers)”,这意味着它们让你在它们装饰的函数前后执行代码,而不用修改函数本身。

手工装饰

你怎么做手动:

 # A decorator is a function that expects ANOTHER function as parameter def my_shiny_new_decorator(a_function_to_decorate): # Inside, the decorator defines a function on the fly: the wrapper. # This function is going to be wrapped around the original function # so it can execute code before and after it. def the_wrapper_around_the_original_function(): # Put here the code you want to be executed BEFORE the original function is called print("Before the function runs") # Call the function here (using parentheses) a_function_to_decorate() # Put here the code you want to be executed AFTER the original function is called print("After the function runs") # At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED. # We return the wrapper function we have just created. # The wrapper contains the function and the code to execute before and after. It's ready to use! return the_wrapper_around_the_original_function # Now imagine you create a function you don't want to ever touch again. def a_stand_alone_function(): print("I am a stand alone function, don't you dare modify me") a_stand_alone_function() #outputs: I am a stand alone function, don't you dare modify me # Well, you can decorate it to extend its behavior. # Just pass it to the decorator, it will wrap it dynamically in # any code you want and return you a new function ready to be used: a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() #outputs: #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 。 这很简单,只需使用a_stand_alone_function返回的函数覆盖my_shiny_new_decorator

 a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function() #outputs: #Before the function runs #I am a stand alone function, don't you dare modify me #After the function runs # That's EXACTLY what decorators do! 

装饰者揭秘

前面的示例使用装饰器语法:

 @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) 

装饰者只是装饰者devise模式的pythonic变体。 Python中embedded了几种经典的devise模式以简化开发(如迭代器)。

当然,你可以积累装饰:

 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() #outputs: --ham-- sandwich = bread(ingredients(sandwich)) sandwich() #outputs: #</''''''\> # #tomatoes# # --ham-- # ~salad~ #<\______/> 

使用Python装饰器语法:

 @bread @ingredients def sandwich(food="--ham--"): print(food) sandwich() #outputs: #</''''''\> # #tomatoes# # --ham-- # ~salad~ #<\______/> 

你设置装饰器的顺序问题:

 @ingredients @bread def strange_sandwich(food="--ham--"): print(food) strange_sandwich() #outputs: ##tomatoes# #</''''''\> # --ham-- #<\______/> # ~salad~ 

现在:回答这个问题

作为结论,你可以很容易地看到如何回答这个问题:

 # The decorator to make it bold def makebold(fn): # The new function the decorator returns def wrapper(): # Insertion of some code before and after return "<b>" + fn() + "</b>" return wrapper # The decorator to make it italic def makeitalic(fn): # The new function the decorator returns def wrapper(): # Insertion of some code before and after return "<i>" + fn() + "</i>" return wrapper @makebold @makeitalic def say(): return "hello" print(say()) #outputs: <b><i>hello</i></b> # This is the exact equivalent to def say(): return "hello" say = makebold(makeitalic(say)) print(say()) #outputs: <b><i>hello</i></b> 

你现在可以离开快乐,或者多一点点燃你的大脑,看看修饰器的高级用法。


把装修到一个新的水平

将parameter passing给装饰函数

 # It's not black magic, you just have to let the wrapper # pass the argument: def a_decorator_passing_arguments(function_to_decorate): def a_wrapper_accepting_arguments(arg1, arg2): print("I got args! Look: {0}, {1}".format(arg1, arg2)) function_to_decorate(arg1, arg2) return a_wrapper_accepting_arguments # Since when you are calling the function returned by the decorator, you are # calling the wrapper, passing arguments to the wrapper will let it pass them to # the decorated function @a_decorator_passing_arguments def print_full_name(first_name, last_name): print("My name is {0} {1}".format(first_name, last_name)) print_full_name("Peter", "Venkman") # outputs: #I got args! Look: Peter Venkman #My name is Peter Venkman 

装饰方法

关于Python的一个很好的事情就是方法和函数真的是一样的。 唯一的区别是方法期望他们的第一个参数是对当前对象( self )的引用。

这意味着你可以用同样的方法构build一个装饰器。 只要记住要考虑到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 {0}, what did you think?".format(self.age + lie)) l = Lucy() l.sayYourAge(-3) #outputs: I am 26, what did you think? 

如果你正在做通用的装饰器 – 一个你会适用于任何函数或方法,不pipe它的参数 – 然后只使用*args, **kwargs

 def a_decorator_passing_arbitrary_arguments(function_to_decorate): # The wrapper accepts any arguments def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs): print("Do I have args?:") print(args) print(kwargs) # Then you unpack the arguments, here *args, **kwargs # If you are not familiar with unpacking, check: # 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() #outputs #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) #outputs #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 {0}, {1} and {2} like platypus? {3}".format(a, b, c, platypus)) function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!") #outputs #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 {0}, what did you think?".format(self.age + lie)) m = Mary() m.sayYourAge() #outputs # Do I have args?: #(<__main__.Mary object at 0xb7d303ac>,) #{} #I am 28, what did you think? 

将parameter passing给装饰器

太好了,现在你会怎么说将修饰符传递给装饰器本身呢?

这可能会有些扭曲,因为装饰者必须接受一个函数作为参数。 因此,你不能将修饰的函数的参数直接传递给装饰器。

在急于解决问题之前,让我们先写一点提示:

 # Decorators are ORDINARY functions def my_decorator(func): print("I am an ordinary function") def wrapper(): print("I am function returned by the decorator") func() return wrapper # Therefore, you can call it without any "@" def lazy_function(): print("zzzzzzzz") decorated_function = my_decorator(lazy_function) #outputs: I am an ordinary function # It outputs "I am an ordinary function", because that's just what you do: # calling a function. Nothing magic. @my_decorator def lazy_function(): print("zzzzzzzz") #outputs: I am an ordinary function 

完全一样。 “ my_decorator ”被调用。 所以,当你@my_decorator ,你告诉Python调用variables“ 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 # Let's create a decorator. It's just a new function after all. new_decorator = decorator_maker() #outputs: #I make decorators! I am executed only once: when you make me create a decorator. #As a decorator maker, I return a decorator # Then we decorate the function 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 # Let's call the 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. 

这里不足为奇

让我们做同样的事情,但跳过所有讨厌的中间variables:

 def decorated_function(): print("I am the decorated function.") decorated_function = decorator_maker()(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. # Finally: 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. 

让我们更短

 @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. 

嘿,你看到了吗? 我们用“ @ ”语法使用函数调用! 🙂

所以,回到装饰与参数。 如果我们可以使用函数来dynamic生成装饰器,我们可以将parameter passing给该函数,对吧?

 def decorator_maker_with_arguments(decorator_arg1, decorator_arg2): print("I make decorators! And I accept arguments: {0}, {1}".format(decorator_arg1, decorator_arg2)) def my_decorator(func): # The ability to pass arguments here is a gift from closures. # If you are not comfortable with closures, you can assume it's ok, # or read: https://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python print("I am the decorator. Somehow you passed me arguments: {0}, {1}".format(decorator_arg1, decorator_arg2)) # Don't confuse decorator arguments and function arguments! 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") #outputs: #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 

这是一个带参数的装饰器。 参数可以设置为variables:

 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") #outputs: #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 

正如你所看到的,你可以像使用这个技巧的任何函数一样将parameter passing给装饰器。 如果你愿意*args, **kwargs你甚至可以使用*args, **kwargs 。 但是请记住装饰器被调用一次 。 就在Python导入脚本的时候。 之后不能dynamic设置参数。 当你做“import x”时, 这个函数已经被装饰了 ,所以你不能改变任何东西。


让我们练习:装饰一个装饰者

好吧,作为一个奖励,我会给你一个片段,让任何装饰者接受一般的任何论点。 毕竟,为了接受参数,我们使用另一个函数创build了我们的装饰器。

我们包装了装饰者。

还有什么我们最近看到的包装函数?

哦,是的,装修!

让我们有一些乐趣,并为装饰者写一个装饰器:

 def decorator_with_args(decorator_to_enhance): """ This function is supposed to be used as a decorator. It must decorate an other function, that is intended to be used as a decorator. Take a cup of coffee. It will allow any decorator to accept an arbitrary number of arguments, saving you the headache to remember how to do that every time. """ # We use the same trick we did to pass arguments def decorator_maker(*args, **kwargs): # We create on the fly a decorator that accepts only a function # but keeps the passed arguments from the maker. def decorator_wrapper(func): # We return the result of the original decorator, which, after all, # IS JUST AN ORDINARY FUNCTION (which returns a function). # Only pitfall: the decorator must have this specific signature or it won't work: return decorator_to_enhance(func, *args, **kwargs) return decorator_wrapper return decorator_maker 

它可以使用如下:

 # You create the function you will use as a decorator. And stick a decorator on it :-) # Don't forget, the signature is "decorator(func, *args, **kwargs)" @decorator_with_args def decorated_decorator(func, *args, **kwargs): def wrapper(function_arg1, function_arg2): print("Decorated with {0} {1}".format(args, kwargs)) return func(function_arg1, function_arg2) return wrapper # Then you decorate the functions you wish with your brand new decorated decorator. @decorated_decorator(42, 404, 1024) def decorated_function(function_arg1, function_arg2): print("Hello {0} {1}".format(function_arg1, function_arg2)) decorated_function("Universe and", "everything") #outputs: #Decorated with (42, 404, 1024) {} #Hello Universe and everything # Whoooot! 

我知道,最后一次有这种感觉的时候,听到一个人说:“在理解recursion之前,你必须先了解recursion”。 但是现在呢,掌握这个不是很好吗?


最佳实践:装饰者

  • 修饰器是在Python 2.4中引入的,因此请确保您的代码在> = 2.4上运行。
  • 装饰者减慢函数调用。 记住这一点。
  • 你不能去装饰一个function。 (有创build可以被删除,但没有人使用它们的装饰器的黑客。)所以一旦一个函数被装饰,它的装饰所有的代码
  • 装饰器包装function,这可以使他们很难debugging。 (这从Python> = 2.5变得更好;见下文)。

functools模块是在Python 2.5中引入的。 它包含函数functools.wraps() ,它将装饰函数的名称,模块和文档string复制到其包装器中。

(有趣的事实: functools.wraps()是一个装饰器!)

 # For debugging, the stacktrace prints you the function __name__ def foo(): print("foo") print(foo.__name__) #outputs: foo # With a decorator, it gets messy def bar(func): def wrapper(): print("bar") return func() return wrapper @bar def foo(): print("foo") print(foo.__name__) #outputs: wrapper # "functools" can help for that import functools def bar(func): # We say that "wrapper", is wrapping "func" # and the magic begins @functools.wraps(func) def wrapper(): print("bar") return func() return wrapper @bar def foo(): print("foo") print(foo.__name__) #outputs: foo 

装饰者怎么可能有用?

现在最大的问题是:我可以使用装饰器来做什么?

看起来很酷很强大,但是一个实际的例子会很棒。 那么有1000个可能性。 经典用途是从外部库扩展函数行为(不能修改它),或者用于debugging(因为它是暂时的,所以不需要修改)。

您可以使用它们以DRY的方式扩展多个function,如下所示:

 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("{0} {1}".format(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("{0} {1} {2}".format(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!")) #outputs: #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(): 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()) #outputs: #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本身提供了几个装饰器: propertystaticmethod

  • Django使用装饰器来pipe理caching和查看权限。
  • 扭曲以伪造内联asynchronous函数调用。

这真的是一个大型的游乐场。

或者,您可以编写一个工厂函数,该函数返回一个装饰器,该装饰器将装饰函数的返回值封装在传递给工厂函数的标记中。 例如:

 from functools import wraps def wrap_in_tag(tag): def factory(func): @wraps(func) def decorator(): return '<%(tag)s>%(rv)s</%(tag)s>' % ( {'tag': tag, 'rv': func()}) return decorator return factory 

这使您可以编写:

 @wrap_in_tag('b') @wrap_in_tag('i') def say(): return 'hello' 

要么

 makebold = wrap_in_tag('b') makeitalic = wrap_in_tag('i') @makebold @makeitalic def say(): return 'hello' 

就我个人而言,我会写一些装饰器:

 from functools import wraps def wrap_in_tag(tag): def factory(func): @wraps(func) def decorator(val): return func('<%(tag)s>%(val)s</%(tag)s>' % {'tag': tag, 'val': val}) return decorator return factory 

这将产生:

 @wrap_in_tag('b') @wrap_in_tag('i') def say(val): return val say('hello') 

不要忘记装饰器语法是简写的构造:

 say = wrap_in_tag('b')(wrap_in_tag('i')(say))) 

看起来其他人已经告诉你如何解决这个问题。 我希望这会帮助你理解装饰者是什么。

装饰者只是语法上的糖。

这个

 @decorator def func(): ... 

扩展到

 def func(): ... func = decorator(func) 

当然,您也可以从装饰器函数中返回lambdaexpression式:

 def makebold(f): return lambda: "<b>" + f() + "</b>" def makeitalic(f): return lambda: "<i>" + f() + "</i>" @makebold @makeitalic def say(): return "Hello" print say() 

Python装饰器为另一个函数添加额外的function

一个斜体装饰可能会像

 def makeitalic(fn): def newFunc(): return "<i>" + fn() + "</i>" return newFunc 

请注意,函数是在函数内定义的。 它基本上是用新定义的函数replace一个函数。 例如,我有这个class

 class foo: def bar(self): print "hi" def foobar(self): print "hi again" 

现在说,我希望这两个函数在完成之前和之后打印“—”。 我可以在每个打印语句之前和之后添加一个“—”。 但是因为我不喜欢重复自己,所以我会做一个装饰者

 def addDashes(fn): # notice it takes a function as an argument def newFunction(self): # define a new function print "---" fn(self) # call the original function print "---" return newFunction # Return the newly defined function - it will "replace" the original 

所以现在我可以改变我的课程

 class foo: @addDashes def bar(self): print "hi" @addDashes def foobar(self): print "hi again" 

有关装饰器的更多信息,请查看http://www.ibm.com/developerworks/linux/library/l-cpdecor.html

做同样事情的另一种方法是:

 class bol(object): def __init__(self, f): self.f = f def __call__(self): return "<b>{}</b>".format(self.f()) class ita(object): def __init__(self, f): self.f = f def __call__(self): return "<i>{}</i>".format(self.f()) @bol @ita def sayhi(): return 'hi' 

或者更灵活:

 class sty(object): def __init__(self, tag): self.tag = tag def __call__(self, f): def newf(): return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag) return newf @sty('b') @sty('i') def sayhi(): return 'hi' 

可以做两个单独的装饰器来做你想做的,就像下面直接说明的那样。 注意在*args, **kwargs wrapped()函数的声明中使用*args, **kwargs ,它支持具有多个参数的装饰函数(这对于示例say()函数并不是必须的,但是包含在通用性中)。

由于类似的原因, functools.wraps装饰器被用来将被包装的函数的元属性改为正在装饰的元素的属性。 这使得错误消息和embedded式函数文档( func.__doc__ )成为装饰函数而不是wrapped()的。

 from functools import wraps def makebold(fn): @wraps(fn) def wrapped(*args, **kwargs): return "<b>" + fn(*args, **kwargs) + "</b>" return wrapped def makeitalic(fn): @wraps(fn) def wrapped(*args, **kwargs): return "<i>" + fn(*args, **kwargs) + "</i>" return wrapped @makebold @makeitalic def say(): return 'Hello' print(say()) # -> <b><i>Hello</i></b> 

Refinements

As you can see there's a lot of duplicate code in these two decorators. Given this similarity it would be better for you to instead make a generic one that was actually a decorator factory —in other words, a decorator that makes other decorators. That way there would be less code repetition—and allow the DRY principle to be followed.

 def html_deco(tag): def decorator(fn): @wraps(fn) def wrapped(*args, **kwargs): return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag return wrapped return decorator @html_deco('b') @html_deco('i') def greet(whom=''): # function with keyword argument return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> <b><i>Hello world</i></b> 

To make the code more readable, you can assign a more descriptive name to the factory-generated decorators:

 makebold = html_deco('b') makeitalic = html_deco('i') @makebold @makeitalic def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> <b><i>Hello world</i></b> 

or even combine them like this:

 makebolditalic = lambda fn: makebold(makeitalic(fn)) @makebolditalic def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> <b><i>Hello world</i></b> 

效率

While the above examples do all work, the code generated involves a fair amount of overhead in the form of extraneous function calls when multiple decorator are applied at once. This may not matter, depending the exact usage (which might be I/O-bound, for instance).

If speed of the decorated function is important, the overhead can be kept to a single extra function call by writing a slightly different decorator factory-function which implements adding all the tags at once, so it can generate code that avoids the addtional function calls incurred by using separate decorators for each tag.

This requires more code in the decorator itself, but this only runs when it's being appled to function definitions, not later when they themselves are called. This also applies when creating more readable names by using lambda functions as previously illustrated. 样品:

 def multi_html_deco(*tags): start_tags, end_tags = [], [] for tag in tags: start_tags.append('<%s>' % tag) end_tags.append('</%s>' % tag) start_tags = ''.join(start_tags) end_tags = ''.join(reversed(end_tags)) def decorator(fn): @wraps(fn) def wrapped(*args, **kwargs): return start_tags + fn(*args, **kwargs) + end_tags return wrapped return decorator makebolditalic = multi_html_deco('b', 'i') @makebolditalic def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print greet('world') # -> <b><i>Hello world</i></b> 

How can I make two decorators in Python that would do the following?

You want the following function, when called:

 @makebold @makeitalic def say(): return "Hello" 

回来:

 <b><i>Hello</i></b> 

解决scheme简单

To most simply do this, make decorators that return lambdas (anonymous functions) that close over the function (closures) and call it:

 def makeitalic(fn): return lambda: '<i>' + fn() + '</i>' def makebold(fn): return lambda: '<b>' + fn() + '</b>' 

Now use them as desired:

 @makebold @makeitalic def say(): return 'Hello' 

and now:

 >>> say() '<b><i>Hello</i></b>' 

Problems with the simple solution

But we seem to have nearly lost the original function.

 >>> say <function <lambda> at 0x4ACFA070> 

To find it, we'd need to dig into the closure of each lambda, one of which is buried in the other:

 >>> say.__closure__[0].cell_contents <function <lambda> at 0x4ACFA030> >>> say.__closure__[0].cell_contents.__closure__[0].cell_contents <function say at 0x4ACFA730> 

So if we put documentation on this function, or wanted to be able to decorate functions that take more than one argument, or we just wanted to know what function we were looking at in a debugging session, we need to do a bit more with our wrapper.

Full featured solution – overcoming most of these problems

We have the decorator wraps from the functools module in the standard library!

 from functools import wraps def makeitalic(fn): # must assign/update attributes from wrapped function to wrapper # __module__, __name__, __doc__, and __dict__ by default @wraps(fn) # explicitly give function whose attributes it is applying def wrapped(*args, **kwargs): return '<i>' + fn(*args, **kwargs) + '</i>' return wrapped def makebold(fn): @wraps(fn) def wrapped(*args, **kwargs): return '<b>' + fn(*args, **kwargs) + '</b>' return wrapped 

It is unfortunate that there's still some boilerplate, but this is about as simple as we can make it.

In Python 3, you also get __qualname__ and __annotations__ assigned by default.

所以现在:

 @makebold @makeitalic def say(): """This function returns a bolded, italicized 'hello'""" return 'Hello' 

And now:

 >>> say <function say at 0x14BB8F70> >>> help(say) Help on function say in module __main__: say(*args, **kwargs) This function returns a bolded, italicized 'hello' 

结论

So we see that wraps makes the wrapping function do almost everything except tell us exactly what the function takes as arguments.

There are other modules that may attempt to tackle the problem, but the solution is not yet in the standard library.

A decorator takes the function definition and creates a new function that executes this function and transforms the result.

 @deco def do(): ... 

is eqivarent to:

 do = deco(do) 

例:

 def deco(func): def inner(letter): return func(letter).upper() #upper return inner 

这个

 @deco def do(number): return chr(number) # number to letter 

is eqivalent to this def do2(number): return chr(number)

 do2 = deco(do2) 

65 <=> 'a'

 print(do(65)) print(do2(65)) >>> B >>> B 

To understand the decorator, it is important to notice, that decorator created a new function do which is inner that executes func and transforms the result.

To explain decorator in a simpler way:

附:

 @decor1 @decor2 def func(*args, **kwargs): pass 

When do:

 func(*args, **kwargs) 

You really do:

 decor1(decor2(func))(*args, **kwargs) 

Speaking of the counter example – as given above, the counter will be shared between all functions that use the decorator:

 def counter(func): def wrapped(*args, **kws): print 'Called #%i' % wrapped.count wrapped.count += 1 return func(*args, **kws) wrapped.count = 0 return wrapped 

That way, your decorator can be reused for different functions (or used to decorate the same function multiple times: func_counter1 = counter(func); func_counter2 = counter(func) ), and the counter variable will remain private to each.

Here is a simple example of chaining decorators. Note the last line – it shows what is going on under the covers.

 ############################################################ # # decorators # ############################################################ def bold(fn): def decorate(): # surround with bold tags before calling original function return "<b>" + fn() + "</b>" return decorate def uk(fn): def decorate(): # swap month and day fields = fn().split('/') date = fields[1] + "/" + fields[0] + "/" + fields[2] return date return decorate import datetime def getDate(): now = datetime.datetime.now() return "%d/%d/%d" % (now.day, now.month, now.year) @bold def getBoldDate(): return getDate() @uk def getUkDate(): return getDate() @bold @uk def getBoldUkDate(): return getDate() print getDate() print getBoldDate() print getUkDate() print getBoldUkDate() # what is happening under the covers print bold(uk(getDate))() 

The output looks like:

 17/6/2013 <b>17/6/2013</b> 6/17/2013 <b>6/17/2013</b> <b>6/17/2013</b> 
 #decorator.py def makeHtmlTag(tag, *args, **kwds): def real_decorator(fn): css_class = " class='{0}'".format(kwds["css_class"]) \ if "css_class" in kwds else "" def wrapped(*args, **kwds): return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">" return wrapped # return decorator dont call it return real_decorator @makeHtmlTag(tag="b", css_class="bold_css") @makeHtmlTag(tag="i", css_class="italic_css") def hello(): return "hello world" print hello() 

You can also write decorator in Class

 #class.py class makeHtmlTagClass(object): def __init__(self, tag, css_class=""): self._tag = tag self._css_class = " class='{0}'".format(css_class) \ if css_class != "" else "" def __call__(self, fn): def wrapped(*args, **kwargs): return "<" + self._tag + self._css_class+">" \ + fn(*args, **kwargs) + "</" + self._tag + ">" return wrapped @makeHtmlTagClass(tag="b", css_class="bold_css") @makeHtmlTagClass(tag="i", css_class="italic_css") def hello(name): return "Hello, {}".format(name) print hello("Your name") 

Decorate functions with different number of arguments:

 def frame_tests(fn): def wrapper(*args): print "\nStart: %s" %(fn.__name__) fn(*args) print "End: %s\n" %(fn.__name__) return wrapper @frame_tests def test_fn1(): print "This is only a test!" @frame_tests def test_fn2(s1): print "This is only a test! %s" %(s1) @frame_tests def test_fn3(s1, s2): print "This is only a test! %s %s" %(s1, s2) if __name__ == "__main__": test_fn1() test_fn2('OK!') test_fn3('OK!', 'Just a test!') 

结果:

 Start: test_fn1 This is only a test! End: test_fn1 Start: test_fn2 This is only a test! OK! End: test_fn2 Start: test_fn3 This is only a test! OK! Just a test! End: test_fn3