티스토리 뷰

파이썬

[Python] Decorator 개념 정리

글을 쓰는 개발자 2021. 12. 25. 19:22
반응형

파이썬 하면서 가장 많이 쓰이는 것 중 하나가 decorator 인 것 같다. 그래서 한 번 정리해볼려고 한다.

 

우선 데코레이터가 작동하는 원리를 보여줄려고 한다.

 

1. 데코레이터 시작은 어떻게 할까?

def sum_with_plus_decorator(arg):

    def inner(func):
        def core(values):
            return reduce(func, values, power)
        return core
    if callable(arg):
        power = 0
        return inner(arg)
    else:
        power = arg
        return inner


@sum_with_plus_decorator
def add_together(a, b):
    return a + b

 

print(add_together([1, 3, 11, 33, 31, 13, 21, 32, 51, 35]))

다음과 같이 불러 낼 수 있습니다.

 

근데 데코레이터는 어떻게 작동하는 걸까요?

 

위에 있는 sum_with_plus_decorator 함수가 add_together를 변수로 받고 그 반환값이 리스트 인자를 매개변수를 받는 형태이다.

def sum_with_plus_decorator(arg):
    def inner(func):
        def core(values):
            return reduce(func, values, power)

        return core

    if callable(arg):
        power = 0
        return inner(arg)
    else:
        power = arg
        return inner


def add_together(a, b):
    return a + b


print(sum_with_plus_decorator(add_together)([1, 3, 11, 33, 31, 13, 21, 32, 51, 35]))

데코레이터 없이 사용한다면 다음과 같이 사용할 수 있습니다.

 

power가 있을 때는 어떻게 받냐구요?

 

print(sum_with_plus_decorator(2)(add_together)([1, 3, 11, 33, 31, 13, 21, 32, 51, 35]))

다음과 같이 작성할 수 있습니다.

 

그림으로 나타내면 다음과 같이 나타낼 수 있습니다.

 

 

2. 데코레이터가 여러개일 때는 아래부터? 위부터?

def decorator_top(func):
    def core(*args, **kwargs):
        print(f'top decorator start')
        ret = func(*args, **kwargs)
        print(f'top decorator finish')
        return ret

    return core


def decorator_bottom(func):
    def core(*args, **kwargs):
        print(f'bottom decorator start')
        ret = func(*args, **kwargs)
        print(f'bottom decorator finish')
        return ret

    return core


@decorator_top
@decorator_bottom
def my_func():
    pass

 

코드는 다음과 같이 작성하였으며 실행 했을 때 결과값은 어떻게 나왔을 까?

함수 호출했을 때의 결과와 같다는 것을 알 수 있다. 일반적으로 함수를 호출 했을 때처럼 스택형식으로 차곡차곡 쌓아서 마지막에 불러온 함수를 해결하면서 마지막에 처음에 호출했던 함수를 불러오는 식처럼 사용됨을 알 수 있다.

 

위의 함수도 다음과 같이 작성할 수 있다.

 

decorator_top(decorator_bottom(func))()

결과가 똑같음을 알 수 있다.

 

 

연습문제

만약에 우리가 문자열과 숫자가 섞여 있는 리스트를 받았고알파벳은 걸러내고, 숫자로된 문자 또는 숫자로 된 리스트로 반환 받고 싶다 (단 데코레이터는 무조건 사용해야 한다!)

생각해보고 밑의 접은 글을 풀어보세요.

 

더보기
def filter_alphabet(fun):
    def inner(val: list):
        val = list(filter(lambda x: (type(x) == int or x.isdigit()), val))
        return fun(val)

    return inner


@filter_alphabet
def convert_integer_list(val: list):
    return list(map(int, val))

 

values = ['1', 2, '3', 4, 'r']
ret = convert_integer_list(values)
print(ret)

물론 이것도 다음과 같이 사용할 수 있다.

 

def filter_alphabet(fun):
    def inner(val: list):
        val = list(filter(lambda x: (type(x) == int or x.isdigit()), val))
        return fun(val)

    return inner


def convert_integer_list(val: list):
    return list(map(int, val))


values = ['1', 2, '3', 4, 'r']
ret = filter_alphabet(convert_integer_list)(values)
print(ret)

 

슬슬 이제 데코레이터에 대해서 감이 오시나요?

 

아직 안오시더라도 괜찮습니다. 아직 밑에 예시가 많거든요 ㅎㅎ

 

3.  framework를 통해 쳐다보는 decorator

1. django에서 흔히 쓰이는 @action [ 1 version 에는 list_route, detail_route )

 

    @action(detail=False, methods=['GET'])
    def recent_memo(self, request):
        recent_memo = Memo.objects.all().order_by('-updated_at')
        serializer = self.get_serializer(recent_memo, many=True)
        return Response(serializer.data)

action 어노테이션을 쓸 때는 다음과 같이 작성할 수 있다. 

action을 어떻게 구성되어 있는지 한 번 볼까요?

 

def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
    methods = ['get'] if (methods is None) else methods
    methods = [method.lower() for method in methods]

    assert detail is not None, (
        "@action() missing required argument: 'detail'"
    )

    # name and suffix are mutually exclusive
    if 'name' in kwargs and 'suffix' in kwargs:
        raise TypeError("`name` and `suffix` are mutually exclusive arguments.")

    def decorator(func):
        func.mapping = MethodMapper(func, methods)

        func.detail = detail
        func.url_path = url_path if url_path else func.__name__
        func.url_name = url_name if url_name else func.__name__.replace('_', '-')

        # These kwargs will end up being passed to `ViewSet.as_view()` within
        # the router, which eventually delegates to Django's CBV `View`,
        # which assigns them as instance attributes for each request.
        func.kwargs = kwargs

        # Set descriptive arguments for viewsets
        if 'name' not in kwargs and 'suffix' not in kwargs:
            func.kwargs['name'] = pretty_name(func.__name__)
        func.kwargs['description'] = func.__doc__ or None

        return func
    return decorator

장고를 좀 쓴다 하면 접근하게 되는 데코레이터입니다. 이 데코레이터를 한 줄로 표현한다면 다음과 같이 표현하실 수 있습니다.

 

action(detail=False)(recent_memo)(request)

이런식으로 작성하는 것을 데코레이터라는 녀석으로 아름답게 사용할 수 있습니다. 

 

이 글을 작성하게 된 이유?

이 글을 작성하게 된 이유는 django의 dispatch에 어떤 제약을 걸고 싶은데 이를 decorator로 설정을 어떻게 할까? 여기에서 시작되었습니다.

물론 이전에도 데코레이터에 대해서 단순한 로그용으로 구형하여 사용한 적은 있었는데 이렇게 deep하게 다뤄본 적은 처음입니다.

 

처음에는 제가 흔히 알고 있던 패턴 방식으로 다음과 같이 작성하였습니다.

 

def decorator(func):
	@wraps(func)
    def inner(*args, **kwargs):
    	# doing something
        ret = func(*args, **kwargs)
        return ret
    return inner

이렇게 했을 때 문제점이 두가지가 있었습니다.

 

  • wraps때문에 전달되는 값이 dispatch 단에서 '__wrapped__'으로 들어가면서 함수에 대한 필요한 값을 바로 꺼낼 수 없어서 유연한 처리를 못했다.
  • 위에서 보시다시피 action 이나 list_route 같이 바로 함수를 변수로 가지지 않는데 위와 같이 구현하게 되면 함수를 변수로 가지면서 dispatch 단에서 데코레이터를 단 메소드에 대해서 필자가 원하는 처리를 못하였습니다.

그래서 제가 참고하게 된것은 위의 @action 계열을 참고하여 작성하였습니다.

 

그래서 다음과 같이 작성하였더니 필자가 원하는 방향으로 이뤄졌습니다.

 

def my_decorator():
    def inner(func):
        # do something
        return func
    return inner

 

클래스로는 데코레이터를 어떻게 쓸까?

 

1. 변수가 있을 때

class Decorator:
    def __init__(self, detail):
        self.detail = detail

    def __call__(self, func):
        def inner(*args, **kwargs):
            ret = func(*args, **kwargs)
            return ret
        return inner


@Decorator(detail=False)
def my_func():
    print("나의 함수")
    pass


my_func()

 

2. 변수 없이 사용할 때

class Decorator:
    def __init__(self, func):
        self.func = func

    def __call__(self,*args, **kwargs):
        ret = self.func(*args, **kwargs)
        return ret


@Decorator
def my_func():
    print("나의 함수")
    pass

 

클래스로 사용할 때에는  '__call__'를 통해 구현할 수 있습니다. 

 

 

 

읽어주셔서 감사합니다.

 

참고: https://towardsdatascience.com/how-to-use-decorators-in-python-by-example-b398328163b 

 

 

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함