다음을 수행하는 두 개의 데코레이터를 Python에서 어떻게 만들 수 있습니까?
@makebold @makeitalic def say(): return "Hello"
... 다음을 반환해야 합니다.
"<b><i>Hello</i></b>"
HTML
이런 식으로 만들려는 것이 아닙니다. 데코레이터와 데코레이터 연결이 어떻게 작동하는지 이해하려고 합니다.
질문자 :Imran
다음을 수행하는 두 개의 데코레이터를 Python에서 어떻게 만들 수 있습니까?
@makebold @makeitalic def say(): return "Hello"
... 다음을 반환해야 합니다.
"<b><i>Hello</i></b>"
HTML
이런 식으로 만들려는 것이 아닙니다. 데코레이터와 데코레이터 연결이 어떻게 작동하는지 이해하려고 합니다.
긴 설명이 필요하지 않은 경우 Paolo Bergantino의 답변을 참조하십시오.
데코레이터를 이해하려면 먼저 함수가 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 as 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 as e: print(e) #outputs : "name 'whisper' is not defined"* #Python's functions are objects
좋아, 아직 여기 있어? 이제 재미있는 부분...
함수가 객체라는 것을 보았습니다. 따라서 기능:
즉 , 함수가 다른 함수를 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!
글쎄, 당신은 데코레이터를 이해하는 데 필요한 모든 것을 가지고 있습니다. 보다시피 데코레이터는 "래퍼"입니다. 즉, 함수 자체를 수정하지 않고 데코레이터가 장식하는 함수 전후에 코드를 실행할 수 있습니다.
수동으로 수행하는 방법:
# 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_decorated
가 대신 호출되기를 원할 것입니다. 즉, 쉽게 그냥 덮어 쓰기의 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)
데코레이터는 데코레이터 디자인 패턴 의 파이썬 변형일 뿐입니다. (반복자와 같은) 개발을 용이하게 하기 위해 Python에 내장된 몇 가지 고전적인 디자인 패턴이 있습니다.
물론 데코레이터를 축적할 수 있습니다.
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>
이제 그냥 행복하게 지내거나 두뇌를 조금 더 태우고 데코레이터의 고급 사용을 볼 수 있습니다.
# 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
)에 대한 참조라고 예상한다는 것입니다.
즉, 같은 방식으로 메서드에 대한 데코레이터를 빌드할 수 있습니다! 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?
범용 데코레이터를 만들고 있다면 인수에 상관없이 모든 함수나 메서드에 적용할 수 있습니다. 그런 다음 *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?
좋습니다. 이제 데코레이터 자체에 인수를 전달하는 것에 대해 어떻게 말하시겠습니까?
데코레이터가 함수를 인수로 받아들여야 하기 때문에 이것은 다소 꼬일 수 있습니다. 따라서 데코레이터에 직접 데코레이팅된 함수의 인수를 전달할 수 없습니다.
해결책을 서두르기 전에 약간의 알림을 작성해 보겠습니다.
# 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에 '변수 " my_decorator "'로 레이블이 지정된 함수를 호출하도록 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.
놀라지 마세요.
똑같은 일을 하되 성가신 중간 변수는 모두 건너뜁니다.
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.
야, 그거 봤어? @
" 구문으로 함수 호출을 사용했습니다! :-)
따라서 인수가 있는 데코레이터로 돌아갑니다. 함수를 사용하여 즉시 데코레이터를 생성할 수 있다면 해당 함수에 인수를 전달할 수 있습니다. 그렇죠?
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
다음은 인수가 있는 데코레이터입니다. 인수는 변수로 설정할 수 있습니다.
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
보시다시피, 이 트릭을 사용하는 모든 함수와 마찬가지로 데코레이터에 인수를 전달할 수 있습니다. *args, **kwargs
사용할 수도 있습니다. 그러나 데코레이터는 한 번만 호출된다는 것을 기억하십시오. Python이 스크립트를 가져올 때만. 나중에 인수를 동적으로 설정할 수 없습니다. "import x"를 수행 하면 함수가 이미 장식되어 있으므로 아무 것도 변경할 수 없습니다.
좋습니다. 보너스로 장식자가 일반적으로 모든 인수를 허용하도록 하는 스니펫을 제공하겠습니다. 결국 인수를 받아들이기 위해 다른 함수를 사용하여 데코레이터를 만들었습니다.
데코레이터를 포장했습니다.
우리가 최근에 그 래핑된 함수를 본 다른 것이 있습니까?
오 예, 장식가들!
재미를 가지고 데코레이터를 위한 데코레이터를 작성해 보겠습니다.
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!
마지막으로 이런 느낌을 받았을 때 "재귀를 이해하기 전에 먼저 재귀를 이해해야 합니다"라는 사람의 말을 들은 후였습니다. 하지만 지금, 당신은 이것을 마스터하는 것에 대해 기분이 좋지 않습니까?
functools
모듈은 Python 2.5에서 도입되었습니다. 여기에는 데코레이팅된 함수의 이름, 모듈 및 독스트링을 래퍼에 복사하는 functools.wraps()
함수가 포함됩니다.
(재미있는 사실: 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가지 가능성이 있습니다. 클래식 사용은 외부 lib(수정할 수 없음) 또는 디버깅(임시적이기 때문에 수정하고 싶지 않음)에서 함수 동작을 확장하는 것입니다.
다음과 같이 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("{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!
property
, staticmethod
등의 여러 데코레이터를 제공합니다.
이곳은 정말 큰 놀이터입니다.
데코레이터가 어떻게 작동하는지 문서를 확인하십시오. 요청하신 내용은 다음과 같습니다.
from functools import wraps def makebold(fn): @wraps(fn) def wrapper(*args, **kwargs): return "<b>" + fn(*args, **kwargs) + "</b>" return wrapper def makeitalic(fn): @wraps(fn) def wrapper(*args, **kwargs): return "<i>" + fn(*args, **kwargs) + "</i>" return wrapper @makebold @makeitalic def hello(): return "hello world" @makebold @makeitalic def log(s): return s print hello() # returns "<b><i>hello world</i></b>" print hello.__name__ # with functools.wraps() this returns "hello" print log('hello') # returns "<b><i>hello</i></b>"
또는 팩토리 함수에 전달된 태그에 데코레이팅된 함수의 반환 값을 래핑하는 데코레이터를 반환하는 팩토리 함수를 작성할 수 있습니다. 예를 들어:
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)
물론 데코레이터 함수에서도 람다를 반환할 수 있습니다.
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 데코레이터는 다른 기능에 추가 기능을 추가합니다.
기울임꼴 데코레이터는 다음과 같을 수 있습니다.
def makeitalic(fn): def newFunc(): return "<i>" + fn() + "</i>" return newFunc
함수는 함수 내부에 정의되어 있습니다. 기본적으로 함수를 새로 정의된 함수로 교체합니다. 예를 들어 이 수업이 있습니다.
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을 확인하십시오.
바로 아래 그림과 같이 원하는 작업을 수행하는 두 개의 별도 데코레이터를 만들 수 있습니다. 다중 인수를 갖는 데코레이팅된 함수를 지원 wrapped()
함수의 선언에서 *args, **kwargs
의 사용에 주목하십시오 say()
함수에는 실제로 필요하지 않지만 일반성을 위해 포함됨).
비슷한 이유로 functools.wraps
데코레이터는 래핑된 함수의 메타 속성을 데코레이트되는 함수의 속성으로 변경하는 데 사용됩니다. 이것은 오류 메시지와 내장 함수 문서( 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>
보시다시피 이 두 데코레이터에는 중복 코드가 많이 있습니다. 이러한 유사성을 감안할 때 실제로는 데코레이터 팩토리 즉, 다른 데코레이터를 만드는 데코레이터 기능인 일반 하나를 만드는 것이 더 나을 것입니다. 그렇게 하면 코드 반복이 줄어들고 DRY 원칙을 따를 수 있습니다.
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')) # -> <b><i>Hello world</i></b>
코드를 더 읽기 쉽게 만들기 위해 공장에서 생성된 데코레이터에 더 설명적인 이름을 할당할 수 있습니다.
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>
또는 다음과 같이 결합할 수도 있습니다.
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>
위의 예제가 모든 작업을 수행하는 동안 생성된 코드는 여러 데코레이터가 한 번에 적용될 때 관련 없는 함수 호출의 형태로 상당한 양의 오버헤드를 포함합니다. 정확한 사용법(예: I/O 바운드일 수 있음)에 따라 중요하지 않을 수 있습니다.
데코레이팅된 함수의 속도가 중요한 경우 한 번에 모든 태그 추가를 구현하는 약간 다른 데코레이터 팩토리 함수를 작성하여 오버헤드를 단일 추가 함수 호출로 유지할 수 있으므로 추가 함수 호출이 발생하는 것을 방지하는 코드를 생성할 수 있습니다. 각 태그에 대해 별도의 데코레이터를 사용합니다.
이를 위해서는 데코레이터 자체에 더 많은 코드가 필요하지만, 이는 나중에 함수 정의 자체가 호출될 때가 아니라 함수 정의에 적용될 때만 실행됩니다. 이는 앞서 설명한 대로 lambda
함수를 사용하여 더 읽기 쉬운 이름을 만들 때도 적용됩니다. 견본:
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>
같은 일을 하는 또 다른 방법:
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'
다음을 수행하는 두 개의 데코레이터를 Python에서 어떻게 만들 수 있습니까?
호출될 때 다음 함수가 필요합니다.
@makebold @makeitalic def say(): return "Hello"
돌려 주다:
<b><i>Hello</i></b>
가장 간단하게 하려면 함수(클로저)를 닫고 호출하는 람다(익명 함수)를 반환하는 데코레이터를 만들고 호출합니다.
def makeitalic(fn): return lambda: '<i>' + fn() + '</i>' def makebold(fn): return lambda: '<b>' + fn() + '</b>'
이제 원하는 대로 사용하십시오.
@makebold @makeitalic def say(): return 'Hello'
그리고 지금:
>>> say() '<b><i>Hello</i></b>'
그러나 우리는 본래의 기능을 거의 상실한 것 같습니다.
>>> say <function <lambda> at 0x4ACFA070>
그것을 찾으려면 각 람다의 클로저를 파헤쳐야 하며, 그 중 하나는 다른 람다에 묻혀 있습니다.
>>> say.__closure__[0].cell_contents <function <lambda> at 0x4ACFA030> >>> say.__closure__[0].cell_contents.__closure__[0].cell_contents <function say at 0x4ACFA730>
따라서 이 함수에 대한 문서를 작성하거나 둘 이상의 인수를 사용하는 함수를 장식할 수 있기를 원하거나 디버깅 세션에서 보고 있는 함수가 무엇인지 알고 싶다면 싸개.
표준 라이브러리의 functools
모듈에서 데코레이터 wraps
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
여전히 일부 상용구가 있다는 것은 불행한 일이지만 이것은 우리가 할 수 있는 만큼 간단합니다.
Python 3에서는 __qualname__
및 __annotations__
할당됩니다.
그래서 지금:
@makebold @makeitalic def say(): """This function returns a bolded, italicized 'hello'""" return 'Hello'
그리고 지금:
>>> say <function say at 0x14BB8F70> >>> help(say) Help on function say in module __main__: say(*args, **kwargs) This function returns a bolded, italicized 'hello'
그래서 우리는 wraps
이 함수가 인수로 취하는 것을 정확히 알려주는 것을 제외하고는 거의 모든 것을 래핑 함수로 만든다는 것을 알 수 있습니다.
문제를 해결하려고 시도할 수 있는 다른 모듈이 있지만 솔루션은 아직 표준 라이브러리에 없습니다.
데코레이터는 함수 정의를 가져와 이 함수를 실행하고 결과를 변환하는 새 함수를 만듭니다.
@deco def do(): ...
는 다음과 같습니다.
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
이것과 동등하다
def do2(number): return chr(number) do2 = deco(do2)
65 <=> '아'
print(do(65)) print(do2(65)) >>> B >>> B
데코레이터를 이해하려면 데코레이터가 함수를 실행하고 결과를 변환하는 내부의 새로운 함수 do를 생성했다는 점에 주목하는 것이 중요합니다.
#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()
클래스에서 데코레이터를 작성할 수도 있습니다.
#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")
이 답변은 오랫동안 답변을 받았지만 새로운 데코레이터를 쉽고 간결하게 작성하는 데코레이터 클래스를 공유하고 싶습니다.
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)
하나는 이것이 데코레이터의 동작을 매우 명확하게 해주지만 새로운 데코레이터를 매우 간결하게 정의하는 것도 쉽게 만든다고 생각합니다. 위에 나열된 예의 경우 다음과 같이 해결할 수 있습니다.
class MakeBold(Decorator): def call(): return "<b>" + self.method() + "</b>" class MakeItalic(Decorator): def call(): return "<i>" + self.method() + "</i>" @MakeBold() @MakeItalic() def say(): return "Hello"
예를 들어 자동으로 함수가 반복자의 모든 인수에 재귀적으로 적용되도록 하는 데코레이터와 같은 더 복잡한 작업을 수행하는 데 사용할 수도 있습니다.
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]))
어떤 인쇄:
2 {'a': 2, 'b': 4} {2, 4, 6} (2, 4, 6, 8) [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
이 예제는 list
유형을 포함하지 않았으므로 최종 print 문에서 메서드가 목록의 요소가 아니라 목록 자체에 적용됩니다.
다음은 데코레이터를 연결하는 간단한 예입니다. 마지막 줄에 주목하십시오 - 그것은 덮개 아래에서 무슨 일이 일어나고 있는지 보여줍니다.
############################################################ # # 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))()
출력은 다음과 같습니다.
17/6/2013 <b>17/6/2013</b> 6/17/2013 <b>6/17/2013</b> <b>6/17/2013</b>
카운터 예제에 대해 말하자면 - 위에 제공된 것처럼 카운터는 데코레이터를 사용하는 모든 함수 간에 공유됩니다.
def counter(func): def wrapped(*args, **kws): print 'Called #%i' % wrapped.count wrapped.count += 1 return func(*args, **kws) wrapped.count = 0 return wrapped
그렇게 하면 데코레이터를 다른 기능에 재사용할 수 있고(또는 같은 기능을 여러 번 데코레이션하는 데 사용: func_counter1 = counter(func); func_counter2 = counter(func)
) 카운터 변수는 각각에 대해 비공개로 유지됩니다.
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
Paolo Bergantino의 답변 에는 stdlib만 사용한다는 큰 장점이 있으며 데코레이터 인수나 장식된 함수 인수가 없는 이 간단한 예제에서 작동합니다.
그러나 보다 일반적인 경우를 처리하려는 경우 3가지 주요 제한 사항이 있습니다.
makestyle(style='bold')
데코레이터를 만드는 것은 간단하지 않습니다.@functools.wraps
생성된 래퍼 는 서명을 보존하지 않으므로 잘못된 인수가 제공되면 실행을 시작하고 일반적인 TypeError
와 다른 종류의 오류가 발생할 수 있습니다.@functools.wraps
로 생성된 래퍼에서 이름을 기반으로 인수 에 액세스하는 것은 매우 어렵습니다. 실제로 인수는 *args
, **kwargs
에 나타날 수도 있고 전혀 나타나지 않을 수도 있습니다(선택 사항인 경우). 내가 쓴 decopatch
첫 번째 문제를 해결하고, 쓴 makefun.wraps
다른 두를 해결하기를. makefun
decorator
lib와 동일한 트릭을 활용합니다.
다음은 인수가 있는 데코레이터를 생성하여 서명을 보존하는 래퍼를 반환하는 방법입니다.
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
decopatch
는 기본 설정에 따라 다양한 파이썬 개념을 숨기거나 표시하는 두 가지 다른 개발 스타일을 제공합니다. 가장 컴팩트한 스타일은 다음과 같습니다.
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
두 경우 모두 데코레이터가 예상대로 작동하는지 확인할 수 있습니다.
@makestyle @makestyle('i') def hello(who): return "hello %s" % who assert hello('world') == '<b><i>hello world</i></b>'
자세한 내용은 설명서 를 참조하십시오.
데코레이터에 커스텀 파라미터를 추가해야 하는 경우를 추가하고 최종 함수에 전달한 다음 작업합니다.
바로 데코레이터:
def jwt_or_redirect(fn): @wraps(fn) def decorator(*args, **kwargs): ... return fn(*args, **kwargs) return decorator def jwt_refresh(fn): @wraps(fn) def decorator(*args, **kwargs): ... new_kwargs = {'refreshed_jwt': 'xxxxx-xxxxxx'} new_kwargs.update(kwargs) return fn(*args, **new_kwargs) return decorator
그리고 최종 기능:
@app.route('/') @jwt_or_redirect @jwt_refresh def home_page(*args, **kwargs): return kwargs['refreched_jwt']
출처 : http:www.stackoverflow.com/questions/739654/how-to-make-function-decorators-and-chain-them-together
할당 후 예기치 않은 변경 사항을 나열합니다. 이것은 왜 발생하며 어떻게 방지할 수 있습니까? (0) | 2021.10.27 |
---|---|
한 줄에 ArrayList 초기화 (0) | 2021.10.27 |
바이트를 문자열로 변환 (0) | 2021.10.27 |
파이썬에서 파일을 어떻게 복사합니까? (0) | 2021.10.27 |
private 함수나 private 메서드, 필드 또는 내부 클래스가 있는 클래스를 테스트하려면 어떻게 해야 합니까? (0) | 2021.10.27 |