etc./StackOverFlow

함수 데코레이터를 만들고 함께 연결하는 방법은 무엇입니까?

청렴결백한 만능 재주꾼 2021. 10. 27. 23:23
반응형

질문자 :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!

마지막으로 이런 느낌을 받았을 때 "재귀를 이해하기 전에 먼저 재귀를 이해해야 합니다"라는 사람의 말을 들은 후였습니다. 하지만 지금, 당신은 이것을 마스터하는 것에 대해 기분이 좋지 않습니까?


모범 사례: 데코레이터

  • 데코레이터는 Python 2.4에서 도입되었으므로 코드가 >= 2.4에서 실행되는지 확인하십시오.
  • 데코레이터는 함수 호출을 느리게 합니다. 그것을 염두에 두십시오.
  • 기능의 장식을 해제할 수 없습니다. ( 제거할 수 있는 데코레이터를 만드는 방법이 있지만 아무도 사용하지 않습니다.) 따라서 함수가 장식되면 모든 코드에 대해 장식됩니다.
  • 데코레이터는 함수를 래핑하여 디버그하기 어렵게 만들 수 있습니다. (이는 Python >= 2.5에서 더 좋아집니다. 아래를 참조하세요.)

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 등의 여러 데코레이터를 제공합니다.

  • Django는 데코레이터를 사용하여 캐싱 및 보기 권한을 관리합니다.
  • 인라인 비동기 함수 호출을 가짜로 만들었습니다.

이곳은 정말 큰 놀이터입니다.


Community Wiki

데코레이터가 어떻게 작동하는지 문서를 확인하십시오. 요청하신 내용은 다음과 같습니다.

 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>"

Paolo Bergantino

또는 팩토리 함수에 전달된 태그에 데코레이팅된 함수의 반환 값을 래핑하는 데코레이터를 반환하는 팩토리 함수를 작성할 수 있습니다. 예를 들어:

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

Community Wiki

다른 사람들이 이미 문제 해결 방법을 알려준 것 같습니다. 데코레이터가 무엇인지 이해하는 데 도움이 되길 바랍니다.

데코레이터는 구문상의 설탕일 뿐입니다.

이것

 @decorator def func(): ...

확장

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

Unknown

물론 데코레이터 함수에서도 람다를 반환할 수 있습니다.

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

Rune Kaagaard

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을 확인하십시오.


Abhinav Gupta

바로 아래 그림과 같이 원하는 작업을 수행하는 두 개의 별도 데코레이터를 만들 수 있습니다. 다중 인수를 갖는 데코레이팅된 함수를 지원 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>

martineau

같은 일을 하는 또 다른 방법:

 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'

qed

다음을 수행하는 두 개의 데코레이터를 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 이 함수가 인수로 취하는 것을 정확히 알려주는 것을 제외하고는 거의 모든 것을 래핑 함수로 만든다는 것을 알 수 있습니다.

문제를 해결하려고 시도할 수 있는 다른 모듈이 있지만 솔루션은 아직 표준 라이브러리에 없습니다.


Aaron Hall

데코레이터는 함수 정의를 가져와 이 함수를 실행하고 결과를 변환하는 새 함수를 만듭니다.

 @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를 생성했다는 점에 주목하는 것이 중요합니다.


Davoud Taghawi-Nejad

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

nickleefly

이 답변은 오랫동안 답변을 받았지만 새로운 데코레이터를 쉽고 간결하게 작성하는 데코레이터 클래스를 공유하고 싶습니다.

 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 문에서 메서드가 목록의 요소가 아니라 목록 자체에 적용됩니다.


v4gil

다음은 데코레이터를 연결하는 간단한 예입니다. 마지막 줄에 주목하십시오 - 그것은 덮개 아래에서 무슨 일이 일어나고 있는지 보여줍니다.

 ############################################################ # # 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>

resigned

카운터 예제에 대해 말하자면 - 위에 제공된 것처럼 카운터는 데코레이터를 사용하는 모든 함수 간에 공유됩니다.

 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) ) 카운터 변수는 각각에 대해 비공개로 유지됩니다.


marqueed

다른 수의 인수로 함수를 장식합니다.

 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

rabin utam

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>'

자세한 내용은 설명서 를 참조하십시오.


smarie

데코레이터에 커스텀 파라미터를 추가해야 하는 경우를 추가하고 최종 함수에 전달한 다음 작업합니다.

바로 데코레이터:

 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']

Alexey Nikonov

출처 : http:www.stackoverflow.com/questions/739654/how-to-make-function-decorators-and-chain-them-together

반응형