데코레이터 이해를 위한 여정 feat FastAPI

L-cloud·2022년 10월 11일
0

파이썬

목록 보기
2/4
post-thumbnail

사전지식

파이썬 함수는 객체다!

def shout(word="yes"):
    return word.capitalize()+"!"
print(shout()) #'Yes!'

scream = shout # 함수 할당

print(scream()) # 'Yes!'

del shout # 함수 지움
try:
    print(shout())
except NameError as e:
    print(e)
    #outputs: "name 'shout' is not defined"

print(scream()) # 'Yes!' 접근 가능!

def doSomethingBefore(func): 
    print("I do something before then I call the function you gave me")
    def inner():
        print('innner')
    print(func())
    return inner

doSomethingBefore(scream)() 
#I do something before then I call the function you gave me
# Yes!
# inner

데코레이터를 이해하기 전에 알아둘 내용

  1. 함수는 변수에 할당될 수 있다.
  2. 함수는 함수 안에서 정의 될 수 있다.
  3. 함수는 return 값이 될 수 있다.
  4. 함수의 파라미터로 함수를 받을 수 있다.

위 코드에 다 나와있다.

  • 데코레이터란?

    decorators are “wrappers”, which means that they let you execute code before and after the function they decorate without modifying the function itself.

    즉 데코레이터란 함수 시작 전, 후에 코드를 실행해주는 wrapper라고 생각해주면 된다.

데코레이터 기초

손으로 만드는 데코레이터와 실제 데코레이터를 살펴보자

def my_new_decorator(a_function_to_decorate):

    def the_wrapper_around_the_original_function():

        print("Before the function runs")

        a_function_to_decorate()

        print("After the function runs")

    return the_wrapper_around_the_original_function

def a_stand_alone_function():
    print("I am a stand alone function, don't you dare modify me")

a_stand_alone_function_decorated = my_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

@my_new_decorator # 데코레이터! 위에서 함수에 함수를 넣어준 코드와 동일한 역할을 한다!
def another_stand_alone_function():
    print("Leave me alone")
another_stand_alone_function()  
#Before the function runs
#Leave me alone
#After the function runs

@func을 하게 되면 코드를 감싸는 것과 동일해진다! 함수에 함수를 넣어준다고 생각하면 된다!

데코레이터는 여러개 못 사용할까? 사용할 수 있다! 함수를 파라미터로 받은 함수를 다시 파라미터로 받은 함수를 생각해보자

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 = bread(ingredients(sandwich))
sandwich()

#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

이제 위 코드를 간단하게 바꿔보자

# sandwich = bread(ingredients(sandwich))

@bread
@ingredients
def sandwich(food="--ham--"):
    print(food)

breadingredient를 넣고 ingredientsandwich를 넣어주었다.


파라미터를 필요로하는 함수 테코레이팅 해주기

천천히 가보자. 데코레이팅 당하는 함수에 파라미터를 넘겨 주는 것 부터 보자!


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


@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") # -> a_wrapper_accepting_arguments("Peter", "Venkman")이렇게 전달 된다.
#I got args! Look: Peter Venkman
#My name is Peter Venkman

#데코레이터가 없다는 가정하에

a  = a_decorator_passing_arguments(print_full_name) # return a_wrapper_accepting_arguments

print_full_name == 데코레이터 없는 a이라고할 수 있다. aa_wrapper_accepting_arguments를 return하기 때문에 print_full_name에 파라미터를 넣어주기 위해서는 a_wrapper_accepting_arguments에 인자를 넣어줘야 한다!

메소드에서도 사용할 수 있다. 처음 파라미터가 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)
#I am 26, what did you think?

데코레이터가 없고 method_friendly_decorator(sayYourAge) 이렇게 있다고 생각해보자. wrapper에서 return이 없으면 어떻게 될가? sayYourAge가 실행되지 않을 것이다.

이제 데코레이터에 파라미터를 넘기러 가보자! 아래 코드의 과정을 하나하나 따라가다보면 데코레이터에 인자를 넘겨주는 방법을 이해할 수 있다.

def decorator_maker():
    
    print("I make decorators! I am executed only once: "
          "when you make me create a decorator.")
            
    def my_decorator(func):
        
        print("I am a decorator! I am executed only when you decorate a function.")
               
        def wrapped():
            print("I am the wrapper around the decorated function. "
                  "I am called when you call the decorated function. "
                  "As the wrapper, I return the RESULT of the decorated function.")
            return func()
        
        print("As the decorator, I return the wrapped function.")
        
        return wrapped
    
    print("As a decorator maker, I return a decorator")
    return my_decorator
            

new_decorator = decorator_maker() # -> my_decorator
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator

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

위 과정이 아래 코드와 동일하다.

@decorator_maker() # decorateor_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.

데코레이터에 파라미터 넣어주기

이제 실제 파라미터를 넣어주자. 최상위 함수에서 파라미터를 받기 때문에 어디서나 인자를 사용할 수 있다!
wrapped, decorator_maker_with_argumets 둘 다 파라미터를 받았다는 사실을 염두하자! wrapped는 데코레이터를 받는 함수에 대한 파라미터이고, decorator_maker_with_argumets는 데코레이터에 대한 파라미터다.
decorator_arg1, decorator_arg2 대신 *args, **kwargs를 사용해도 된다.

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

# I make decorators! And I accept arguments: Leonard, Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard, Sheldon
          
decorated_function_with_arguments("Rajesh", "Howard")

#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

데코레이터 꾸미기

데코레이터를 데코레이터로 꾸며보자!

주의! 머리가 아플 수 있음

def decorator_with_args(decorator_to_enhance):
    def decorator_maker(*args, **kwargs):
   
        def decorator_wrapper(func):
       
            return decorator_to_enhance(func, *args, **kwargs)
        
        return decorator_wrapper
    
    return decorator_maker


@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
    

@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

이해하기까지 꽤나 시간이 걸렸다. 코드를 한줄 한 줄 따라가며 생각해보자. @decorator_with_args가 제일 먼저 호출되며decorator_to_enhance여기에 decorated_decorator가 들어가고 decorator_maker를 반환 받을 것이다. 그 다음에는 @decorated_decorator(42, 404, 1024)를 호출하며 decorator_maker에 (42,404,1024)를 파라미터로 넣으며 함수를 싱행 시킨다. 그 결과decorator_wrapper가 실행되고 decorator_wrapperfunc파라미터에 decorated_function이 들어간다. 그리고 decorator_to_enhance(func, *args, **kwargs)를 호출하게 되니 이는 wrapper를 반환한다.
decorated_function("Universe and", "everything")를 싱행하게 되면 wrapper가 호출 되면서 print를 하고 func를 호출하여 decorated_funcion을 호출한다.
아래는 같은 코드에 주석을 달아보았다.

def decorator_with_args(decorator_to_enhance):
    def decorator_maker(*args, **kwargs):
        print('@decorated_decorator(42, 404, 1024) 실행하면 (42,404,1024) 파라미터 여기서 받음')
        def decorator_wrapper(func):
            print('func', func)
            print("@decorated_decorator(42, 404, 1024)는 이 함수 실행까지 아래 return도 실행함 ")
            return decorator_to_enhance(func, *args, **kwargs) 
        
        return decorator_wrapper
    print("first_start, @decorator_with_args 실행때 여기 까지")
    return decorator_maker


@decorator_with_args 
def decorated_decorator(func, *args, **kwargs): 
    def wrapper(function_arg1, function_arg2):
        print("decorated_function(Universe and, everything) 실행하면 여기 들어와서 나머지 모두 실행" )
        print("Decorated with {0} {1}".format(args, kwargs))
        return func(function_arg1, function_arg2) # decorated_function 실행함
    print("@decorated_decorator(42, 404, 1024) 실행 중 아래 wrapper를 return 값으로 가지고 있음!")
    return wrapper
    

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

FastAPI @app.get('/')

그럼 이제 아래 코드를 이해할 수 있다.

from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
async def main(request: Request,message:Union[None, str] = None):
    return templates.TemplateResponse('main.html',{"request" : request,"message" :message})

FastAPI 코드를 자세히 보면 알 수 있겠지만 아마 아래와 같은 코드가 아닐까?

def get(*param, **kwargs):
    	# do somthing with param,kwargs
    print(param,kwargs)
    def decorator(func):
    	
        def wrapper(*args):
            return func(args)
    	
        return wrapper
    
    return decorator
   

이를 실행해봄 코드

@get('/') # ('/',) {}
def test(*args):
    print(args)


test(33) # ((33,),)

이해가 되었기를 바라며!


사실 아래 질문과 답변에 모든 것이 나와있다
코드출처 및 참고링크

profile
내가 배운 것 정리

0개의 댓글