如何在Python中创建两个装饰器来执行以下操作?
1 2 3 4 | @makebold @makeitalic def say(): return"Hello" |
…它应该返回:
1 | "Hello" |
我并没有试图在实际应用程序中以这种方式创建
如果你不喜欢冗长的解释,看看保罗·波甘蒂诺的回答。
装饰基础
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 26 27 28 | 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函数的另一个有趣的特性是,它们可以在另一个函数中定义!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 |
函数引用
还在这里?现在有趣的部分…
您已经看到函数是对象。因此,功能:
可以赋值给一个变量吗可以在另一个函数中定义吗这意味着一个函数可以
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(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... |
还有更多!
如果你可以
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) #outputs: #I do something before then I call the function you gave me #Yes! |
你只需要了解所有需要了解的装饰器。您可以看到,decorator是"包装器",这意味着它们允许您在修饰函数之前和之后执行代码,而无需修改函数本身。
手工decorator
如何手动操作:
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 | # 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 |
现在,您可能希望每次调用
1 2 3 4 5 6 7 8 | 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! |
decorator启发
在前面的例子中,使用了decorator语法:
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() #outputs: #Before the function runs #Leave me alone #After the function runs |
是的,就是这么简单。
1 | another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function) |
装饰器只是装饰器设计模式的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 26 27 | 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装饰器语法:
1 2 3 4 5 6 7 8 9 10 11 12 | @bread @ingredients def sandwich(food="--ham--"): print(food) sandwich() #outputs: #</''''''\> # #tomatoes# # --ham-- # ~salad~ #<\______/> |
设置装饰器的顺序很重要:
1 2 3 4 5 6 7 8 9 10 11 12 | @ingredients @bread def strange_sandwich(food="--ham--"): print(food) strange_sandwich() #outputs: ##tomatoes# #</''''''\> # --ham-- #<\______/> # ~salad~ |
Now: to answer the question
作为一个结论,你可以很容易地看到如何回答这个问题:
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 | # 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"" + fn() +"" 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"" + fn() +"" return wrapper @makebold @makeitalic def say(): return"hello" print(say()) #outputs: hello # This is the exact equivalent to def say(): return"hello" say = makebold(makeitalic(say)) print(say()) #outputs: hello |
现在,你可以开心地离开,或者让你的大脑更兴奋一点,看看装饰器的高级用法。
将装饰器带到下一层将参数传递给修饰函数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 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的一个妙处是,方法和函数实际上是相同的。唯一的区别是,方法期望它们的第一个参数是对当前对象的引用(
这意味着您可以以同样的方式为方法构建装饰器!只要记住把
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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? |
如果你正在做一个通用的装饰器——一个你可以应用于任何函数或方法的装饰器,不管它的参数是什么——那么就用
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 | 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? |
将参数传递给装饰器
很好,现在您如何看待将参数传递给decorator本身呢?
这可能有点扭曲,因为装饰器必须接受函数作为参数。因此,不能将修饰函数的参数直接传递给修饰器。
在急着解决之前,我们先写个小提示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # 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 |
完全一样。被称为"
这是很重要的!你给的标签可以直接指向装饰师,也可以不指向。
让我们邪恶。吗?
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 # 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. |
这里没有惊喜。
让我们做同样的事情,但跳过所有讨厌的中间变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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. |
让我们把它更短:
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.") #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. |
你看到了吗?我们使用了一个带有"
回到装饰器的参数。如果我们可以使用函数动态地生成装饰器,我们就可以将参数传递给那个函数,对吧?
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") #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 know about my arguments: Leslie Howard |
可以看到,可以像使用此技巧的任何函数一样将参数传递给decorator。如果您愿意,您甚至可以使用
好的,作为一个额外的好处,我将给您一个代码片段,使任何decorator一般地接受任何参数。毕竟,为了接受参数,我们使用另一个函数创建了装饰器。
我们包装了装饰器。
最近我们还看到了其他包装函数吗?
哦,是的,decorator !
让我们有一些乐趣,并编写一个装饰为装饰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |
它的用途如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 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! |
我知道,你上次有这种感觉是在听一个人说:"在理解递归之前,你必须先理解递归。"但是现在,你不觉得掌握这个很好吗?
最佳实践:decorator
装饰器是在Python 2.4中引入的,所以请确保您的代码将在>= 2.4上运行。装饰器降低了函数调用的速度。记住这一点。不能取消对函数的修饰。(有一些技巧可以创建可以删除的装饰器,但没有人使用它们。)因此,一旦一个函数被修饰,它就会为所有代码进行修饰。装饰器包装函数,这使得它们很难调试。(Python >= 2.5使这一点更好;见下文)。
(有趣的事实:
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 | # 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种可能。典型的用法是从外部库扩展函数行为(您不能修改它),或者用于调试(您不想修改它,因为它是临时的)。
你可以用它们来扩展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 55 56 | 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 |
当然,使用decorator的好处是,您可以立即在几乎任何地方使用它们,而无需重写。干,我说:
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 | @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("")[1].split("")[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本身提供了几个装饰器:
这真是个大操场。
查看文档,了解装饰器是如何工作的。这是你要的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from functools import wraps def makebold(fn): @wraps(fn) def wrapped(): return"" + fn() +"" return wrapped def makeitalic(fn): @wraps(fn) def wrapped(): return"" + fn() +"" return wrapped @makebold @makeitalic def hello(): return"hello world" print hello() # returns"hello world" print hello.__name__ # with functools.wraps() this returns"hello". |
或者,您可以编写一个工厂函数,该函数返回一个装饰器,该装饰器将被装饰函数的返回值包装在传递给工厂函数的标记中。例如:
1 2 3 4 5 6 7 8 9 10 | 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 |
这使你能够写:
1 2 3 4 | @wrap_in_tag('b') @wrap_in_tag('i') def say(): return 'hello' |
或
1 2 3 4 5 6 7 | makebold = wrap_in_tag('b') makeitalic = wrap_in_tag('i') @makebold @makeitalic def say(): return 'hello' |
就我个人而言,我可能会以稍微不同的方式编写decorator:
1 2 3 4 5 6 7 8 9 10 | 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 |
这将会产生:
1 2 3 4 5 | @wrap_in_tag('b') @wrap_in_tag('i') def say(val): return val say('hello') |
不要忘记装饰器语法是一种缩写的构造:
1 | say = wrap_in_tag('b')(wrap_in_tag('i')(say))) |
看起来其他人已经告诉你如何解决这个问题了。我希望这能帮助您理解什么是装饰师。
修饰符只是语法上的糖。
这
1 2 3 | @decorator def func(): ... |
扩大到
1 2 3 | def func(): ... func = decorator(func) |
当然你也可以从装饰函数返回lambdas:
1 2 3 4 5 6 7 8 9 10 11 | def makebold(f): return lambda:"" + f() +"" def makeitalic(f): return lambda:"" + f() +"" @makebold @makeitalic def say(): return"Hello" print say() |
Python装饰器向另一个函数添加额外的功能
斜体装饰器可能是这样的
1 2 3 4 | def makeitalic(fn): def newFunc(): return"" + fn() +"" return newFunc |
注意,函数是在函数内部定义的。它的基本功能是用新定义的函数替换一个函数。例如,我有这门课
1 2 3 4 5 | class foo: def bar(self): print"hi" def foobar(self): print"hi again" |
现在,我想让两个函数在完成之后和之前都打印"——"。我可以在每个print语句之前和之后添加一个print"——"。但因为我不喜欢重复我自己,我将做一个装饰
1 2 3 4 5 6 7 | 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 |
现在我可以把我的类改成
1 2 3 4 5 6 7 8 | 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
您可以创建两个独立的decorator,它们可以做您想做的事情,如下所示。注意,
出于类似的原因,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from functools import wraps def makebold(fn): @wraps(fn) def wrapped(*args, **kwargs): return"" + fn(*args, **kwargs) +"" return wrapped def makeitalic(fn): @wraps(fn) def wrapped(*args, **kwargs): return"" + fn(*args, **kwargs) +"" return wrapped @makebold @makeitalic def say(): return 'Hello' print(say()) # -> Hello |
细化
正如您所看到的,这两个装饰器中有很多重复的代码。考虑到这种相似性,您最好创建一个通用的,实际上是装饰器工厂的装饰器—换句话说,一个生成其他装饰器的装饰器。这样就可以减少代码的重复,并允许遵循DRY原则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> Hello world |
为了使代码更具可读性,您可以为工厂生成的装饰器分配一个更具描述性的名称:
1 2 3 4 5 6 7 8 9 | makebold = html_deco('b') makeitalic = html_deco('i') @makebold @makeitalic def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> Hello world |
或者像这样组合:
1 2 3 4 5 6 7 | makebolditalic = lambda fn: makebold(makeitalic(fn)) @makebolditalic def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> Hello world |
效率
虽然上面的示例可以完成所有工作,但是当同时应用多个装饰器时,生成的代码会涉及大量额外的函数调用形式的开销。这可能无关紧要,取决于确切的用法(例如,可能是I/ o绑定)。
如果装饰功能的速度是很重要的,可以保持一个额外的函数调用的开销通过编写一个稍微不同的装饰工厂函数实现添加所有标签,所以它可以生成代码,避免了额外发生函数调用,使用独立的设计师为每个添加标签。
这需要装饰器本身有更多的代码,但这只在将其应用到函数定义时运行,而不是在稍后调用函数定义时运行。这也适用于使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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')) # -> Hello world |
做同样事情的另一种方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class bol(object): def __init__(self, f): self.f = f def __call__(self): return"{}".format(self.f()) class ita(object): def __init__(self, f): self.f = f def __call__(self): return"{}".format(self.f()) @bol @ita def sayhi(): return 'hi' |
或者,更灵活:
1 2 3 4 5 6 7 8 9 10 11 12 | 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' |
How can I make two decorators in Python that would do the following?
你想要以下功能,当调用:
1
2
3
4 @makebold
@makeitalic
def say():
return"Hello"
返回:
1 Hello
简单的解决方案
最简单的方法是,让decorator返回lambda(匿名函数),然后关闭函数(闭包)并调用它:
1 2 3 4 5 | def makeitalic(fn): return lambda: '' + fn() + '' def makebold(fn): return lambda: '' + fn() + '' |
现在根据需要使用它们:
1 2 3 4 | @makebold @makeitalic def say(): return 'Hello' |
现在:
1 2 | >>> say() 'Hello' |
问题的简单解决方案
但我们似乎几乎失去了最初的功能。
1 2 | >>> say <function <lambda> at 0x4ACFA070> |
要找到它,我们需要挖掘每个lambda的闭包,其中一个隐藏在另一个中:
1 2 3 4 | >>> say.__closure__[0].cell_contents <function <lambda> at 0x4ACFA030> >>> say.__closure__[0].cell_contents.__closure__[0].cell_contents <function say at 0x4ACFA730> |
因此,如果我们将文档放在这个函数上,或者希望能够装饰需要多个参数的函数,或者我们只想知道在调试会话中查看的是哪个函数,我们需要对包装器做更多的工作。
全功能解决方案-克服大多数这些问题我们在标准库中有来自
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 '' + fn(*args, **kwargs) + '' return wrapped def makebold(fn): @wraps(fn) def wrapped(*args, **kwargs): return '' + fn(*args, **kwargs) + '' return wrapped |
不幸的是,仍然有一些样板文件,但这是我们能做到的最简单的了。
在python3中,默认情况下还会分配
现在:
1 2 3 4 5 | @makebold @makeitalic def say(): """This function returns a bolded, italicized 'hello'""" return 'Hello' |
现在:
1 2 3 4 5 6 7 | >>> say <function say at 0x14BB8F70> >>> help(say) Help on function say in module __main__: say(*args, **kwargs) This function returns a bolded, italicized 'hello' |
结论
因此,我们看到
还有其他模块可能尝试解决这个问题,但是标准库中还没有解决方案。
装饰器接受函数定义并创建一个新函数来执行该函数并转换结果。
1 2 3 | @deco def do(): ... |
是eqivarent:
1 | do = deco(do) |
例子:
1 2 3 4 | def deco(func): def inner(letter): return func(letter).upper() #upper return inner |
这
1 2 3 | @deco def do(number): return chr(number) # number to letter |
这是等价的吗def洗(数量):返回装备(数量)
1 | do2 = deco(do2) |
65 < = > ' a '
1 2 3 4 | print(do(65)) print(do2(65)) >>> B >>> B |
要理解decorator,很重要的一点是要注意,decorator创建了一个新的函数do,它是执行func并转换结果的内部函数。
用更简单的方式解释decorator:
:
1 2 3 4 | @decor1 @decor2 def func(*args, **kwargs): pass |
什么时候:
1 | func(*args, **kwargs) |
你真的做的事:
1 | decor1(decor2(func))(*args, **kwargs) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #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() |
您还可以在类中编写decorator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #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") |
说到计数器的例子-如上所述,计数器将在所有使用decorator的函数之间共享:
1 2 3 4 5 6 7 | def counter(func): def wrapped(*args, **kws): print 'Called #%i' % wrapped.count wrapped.count += 1 return func(*args, **kws) wrapped.count = 0 return wrapped |
这样,装饰器就可以被不同的函数重用(或者用于多次装饰同一个函数:
下面是链接装饰器的一个简单示例。请注意最后一行—它显示了在幕后发生的事情。
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 | ############################################################ # # decorators # ############################################################ def bold(fn): def decorate(): # surround with bold tags before calling original function return"" + fn() +"" 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))() |
输出如下:
1 2 3 4 5 | 17/6/2013 17/6/2013 6/17/2013 6/17/2013 6/17/2013 |
这个答案早就有了答案,但是我想我应该分享我的Decorator类,它使编写新的Decorator变得简单和紧凑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from abc import ABCMeta, abstractclassmethod class Decorator(metaclass=ABCMeta): """ Acts as a base class for all decorators""" def __init__(self): self.method = None def __call__(self, method): self.method = method return self.call @abstractclassmethod def call(self, *args, **kwargs): return self.method(*args, **kwargs) |
首先,我认为这使装饰器的行为非常清晰,但也使定义新装饰器非常简单。对于上面列出的例子,你可以把它解为:
1 2 3 4 5 6 7 8 9 10 11 12 | class MakeBold(Decorator): def call(): return"" + self.method() +"" class MakeItalic(Decorator): def call(): return"" + self.method() +"" @MakeBold() @MakeItalic() def say(): return"Hello" |
你也可以用它来做更复杂的任务,比如一个装饰器,它自动让函数递归地应用到迭代器中的所有参数:
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 | class ApplyRecursive(Decorator): def __init__(self, *types): super().__init__() if not len(types): types = (dict, list, tuple, set) self._types = types def call(self, arg): if dict in self._types and isinstance(arg, dict): return {key: self.call(value) for key, value in arg.items()} if set in self._types and isinstance(arg, set): return set(self.call(value) for value in arg) if tuple in self._types and isinstance(arg, tuple): return tuple(self.call(value) for value in arg) if list in self._types and isinstance(arg, list): return list(self.call(value) for value in arg) return self.method(arg) @ApplyRecursive(tuple, set, dict) def double(arg): return 2*arg print(double(1)) print(double({'a': 1, 'b': 2})) print(double({1, 2, 3})) print(double((1, 2, 3, 4))) print(double([1, 2, 3, 4, 5])) |
打印:
1 2 3 4 5 | 2 {'a': 2, 'b': 4} {2, 4, 6} (2, 4, 6, 8) [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] |
注意,这个例子没有在装饰器的实例化中包含
Paolo Bergantino的答案具有只使用stdlib的巨大优势,适用于这个简单的示例,其中既没有修饰符参数,也没有修饰函数参数。
然而,如果你想处理更一般的情况,它有3个主要限制:
正如在几个答案中已经指出的,您不能轻松地修改代码来添加可选装饰器参数。例如,创建一个我编写了
这是你如何用参数创建装饰器,返回真正的签名保护包装:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from decopatch import function_decorator, DECORATED from makefun import wraps @function_decorator def makestyle(st='b', fn=DECORATED): open_tag ="<%s>" % st close_tag ="</%s>" % st @wraps(fn) def wrapped(*args, **kwargs): return open_tag + fn(*args, **kwargs) + close_tag return wrapped |
1 2 3 4 5 6 7 | from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS @function_decorator def makestyle(st='b', fn=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS): open_tag ="<%s>" % st close_tag ="</%s>" % st return open_tag + fn(*f_args, **f_kwargs) + close_tag |
在这两种情况下,你都可以检查装饰器是否按预期工作:
1 2 3 4 5 6 | @makestyle @makestyle('i') def hello(who): return"hello %s" % who assert hello('world') == 'hello world' |
详情请参阅文件。
用不同数量的参数修饰函数:
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 frame_tests(fn): def wrapper(*args): print" Start: %s" %(fn.__name__) fn(*args) print"End: %s " %(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!') |
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |