class를 데코레이터로 사용하는 코드를 해석하자

김어진·2021년 1월 19일
0

python

목록 보기
2/2

쓰벌 뭐야 이게

그 내 코드를 보던 중에 이해가 안되는 코드가 있었다.

    class gallery_manager_required:
        '''
        Check what user who send request is manager of gallery.
        '''
        def __init__(self, fn):
            update_wrapper(self, fn)
            self.fn = fn

        def __call__(self, *args, **kargs):
            verify_jwt_in_request()
            request_account = UserModel.get_account_by_email(get_jwt_identity())
            target_gallery = GalleryService.get_gallery_by_id(kargs['gallery_id'])

            if(target_gallery.is_manager(request_account.user) or request_account.is_admin()):
                return self.fn(*args, **kargs)
            else:
                abort(403)

        def __get__(self, instance, owner=None):
            return partial(self.__call__, instance)

이 코드를 작성할 때에도 여러모로 애를 먹었던 기억은 있었다. 디스크립터가 뭔지도 몰랐고 update_wrapper가 뭔지도 몰랐다. 그냥 삽질 하다가 여래 저래 stackoverflow에서 하라는 대로 하니까 되서 냅뒀다. 그 때는 다 이해했다고 생각했는데 역시 그... 기억보다는 기록이다. ㅋㅋ;

step by step

그... 인생도 코드도 step by stpe. 하나하나 천천히 이해 해보자.

update_wrapper()

이건 functools의 함수인데 데코레이터 패턴을 사용하면 원래 함수가 아닌 wrapper 함수를 return하기 때문에 기존 함수의 docstring이나 name등이 소실된다. 그걸 막기 위해서 wrapper함수와 기존 함수를 인자로 받고 wrapper함수의 dict에 기존 함수의 정보를 업데이트 해주는 함수이다. 이는 흔히 볼 수 있는 @wraps의 원래 버전이다.

wraps(func)

wrpas는 wrapper에 감싸지는 기존 함수를 인자로 받은 뒤 partial을 사용해서update_wrapper 함수의 wrapped 파라미터를 업데이트 한 뒤 return하는 함수이다.

    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

이렇게 말이다.

어... 그 뭐야 @some_func(something)는 어떻게 굴러가는거야

나만 그런건지 모르겠는데 저렇게 된 코드가 있으면 some_func라는 함수에 something을 인자로 줘서 실행한 뒤에 return된 값으로 데코레이트를 하는거다. 그니까...

def return_decorator(string):
    def decorator(func):
        print('made by return_decorator is ran' + string)
        
        def wrapper():
            print('wrapper ran')
            return func()

        return wrapper
         
    
    return decorator

@return_decorator('ing gimoring')
def quack():
    print('qruak')

quack()

이렇게 있으면 return_decorator가 먼저 실행되고 데코레이터 함수를 리턴하면, 그 반환된 데코레이터 함수가 quack을 인자로 받아서 데코레이트를 하는 순서인거다.

partial

그럼 partial은 뭔 함수임...
이 함수는 functools에 있는건데 인자로 어떤 함수랑 그 함수의 인자에 맞춰서 keyword argument로 주면, 그거에 맞춰서 받은 함수에 그 인자를 채워서 반환하는 함수이다. 아... 내가 써놓고도 개소리같다. 백문이 불여일견 그냥 코드를 보자.

def print_a_and_b(a, b):
    print(f'a is {a} and b is {b}')

이런 함수가 있다고 해보자... 근데 이제 a의 값은 고정하고 b만 받는 함수를 새로 만들고 싶으면 어떻게 하냐...

def print_10_as_a_and_b(b):
    print_a_and_b(a=10, b=b)

당연히 이렇게 하면 되는데

from functools import partial
print_10_as_a_and_b = partial(print_a_and_b, 10)

이렇게도 할 수 있다 짜잔 ㅎㅎ... 나중에 더 자세히 정리할거다...

그래서 동작은 어떻게...?

음... 다른걸 적다가 약간 벗어났다. 음... 그니까 어떤 함수를 선언 할 때 그 위에@gallery_manager_required를 달면... (편의상 어떤 함수의 이름은 ffunc라고 하자)

  1. gallery_manager_required의 __init__가 ffunc를 인자로 받아 실행된다.
  2. 원래 ffunc의 자리에는 ffunc를 가지고있는 gallery_manager_required가 들어간다.
  3. ffunc()를 하면 gallery_manager_required.__call__()가 실행된다.
  4. 만약 ffunc가 어떤 객체의 속성이여서 some_object.ffunc로 호출되면 gallery_manager_required.__get__()이 호출된다. (gallery_manager_required가 디스크립터의 역할을 했따)

특히 햇갈련던 점.

__get__이 구현되어 있어서 특히 햇갈렸따. 사실 저건 호출 될 일이 없어보였는뎅.

결론

아무튼 겨우겨우 다 이해했따ㅎ... python스킬이 조금 증가했다. 하... 갈 길이 먼데 여기서 너무 오래 삽질한거같다. partial의 구현 코드를 봤는데 *이 잔뜩 있어서 햇갈렸다. 거의 은하수인줄 ㅋㅋ; position argument, keyword argument이런것들에 대해서 좀 알아봐야겠다. 아 맞다. parameter에 /가 붙어있는 것도 봤는데 이것도 뭐인지 알아보자.

0개의 댓글