[wecode TIL] Decorator 개념 정복하기

hyuckhoon.ko·2020년 6월 18일
0

What I learned in wecode

목록 보기
57/109

1. 파이썬 데코레이터 함수

1) 데코레이터란?

  • 함수 decorator
  • 클래스 decorator

데코레이터는 말 그대로 '꾸며주기'다. 무엇을 꾸며주기 위한 것일까.
기존의 정의된(내가 작성해둔) 함수 혹은 클래스를 꾸며주기 위함이다.

그렇다면, 그 꾸밈의 목적은 무엇일까?
기존의 함수와 클래스의 기능을 확장하기 위함이다.

우리의 눈엔 이것이 꾸며주는 것으로 보이지 않을 수도 있다.
쉽게 시작한 파이썬에서 맞딱뜨린 보스몹같은 개념이기에 괴롭히기라는
개념으로 다가오기도 한다.



그렇다면 우리의 상황을 진단해봤을때,
왜 사용하는지, 왜 사용해야만 하는지를 먼저 스스로 납득시켜야 할 것이다.
깃허브에서 확인한 장고 패키지엔 수많은 데코레이터들이 있다.

어려운 개념이라도 정말 필요한 디자인 패턴이기에 차용했을 것이다.






2) 데코레이터 함수 사용 사례

데코레이터를 사용 사례를 먼저 짚고 넘어가야 겠다.

당신은 인스타그램에 접속했다. 무의식적으로 로그인을 하고 있을 것이다.
(이미 로그인되어 있을 수도 있지만.)
인스타그램에서 '좋아요' 혹은 댓글을 올리는 과정에 데코레이터가 사용되고 있다.

  1. 본디 목적 : 웹에 포스팅올리기
  2. 사전 절차 : 이 사용자가 포스팅할 권한이 있는 유저인가? 혹은 로그인 되어 있는가?
    결론 "기존 함수 post에 decorator를 넣어
    사전 절차를 먼저 밟게 해야겠다."

클라이언트가 댓글을 올리거나 블로그를 포스팅하는 것을 쉽게 구현하게 하는 개념이라고 한다.



구글링한 결과,
기존의 코드를 reusable(재사용)가능하게 하기도 하며,
기존의 함수의 기능을 확장시키기까지 하기 때문에 데코레이터가 익숙해 지면
안쓰려고 해도 쓰게되는 파이썬이 제공하는 좋은 디자인 패턴이라고 한다.
(C계열 프로그래밍 언어에서는 없는 개념이다.)







2. 데코레이터 함수 코드

1) 기본 예제

def uppercase_decorator(function):
    def wrapper():
        func = function()
            make_uppercase = func.upper()
            return make_uppercase
    return wrapper
  
@uppercase_decorator
def say_hello():
    return 'hello, world!'
print(say_hello())

우리는 최하단의 함수를 호출했다.
print(decorate())

그리고 @는 데코레이터가 적용되었음을 나타내는 syntax다.

@uppercase_decorator

이는 "내 바로 아래에 있는 함수를 내가 꾸며줄게"의 의미다.

@uppercase_decorator
def say_hello():
    return 'hello, world!'

즉, 꾸미려는 대상은 say_hello 함수다.
끝까지 관점의 주체를 데코레이터에 현혹되서는 안된다.
기존 say_hello함수의 기능을 확장하려고 디자인하는 것이기 때문이다.


즉, 해석하면

say_hello = uppercase_decorator(say_hello)
와 동일한 코드가 된다.

기존 함수(혹은 우리가 작성한 함수)인 say_hello가
uppercase_decorator의 매개변수로 전달됐다.
데코레이터는 say_hello를 function이란 이름의 매개변수로 전달받았다.


이해하기 쉽게 설명하자면 이렇다.
"나 데코레이터는 say_hello함수를 꾸며주기 위해
일단 매개변수로 전달받았어.
이제 say_hello함수로만은 할 수 없었던 기능을 내가 추가(확장)해줄게.
너는 say_hello함수를 수정할 필요없어. 내가 대신해줄테니까 말이야"

(참고로, 파이썬에서 함수는 파라미터가 될 수 있고,
임의의 변수에 assign할 수도 있다. 이를 1st class object(citizen)이라고도 한다.)



이제 데코레이터 함수 안 scope에 정의된 wrapper함수를 보자.
데코레이터가 일 다운 일을 하려고 하기 때문이다.

   def wrapper():
        func = function()
            make_uppercase = func.upper()
            return make_uppercase
    return wrapper

정말 뜬금없이 func라는 변수가 보인다.

func = function()

그리고 func는 function함수를 실행한 결과라고 한다.
즉, func = 'say hello'가 저장돼 있다.

좀전에 언급한 1st class object(citizen)의 개념대로
데코레이터가 say_hello를 function이라는 변수
저장했기에 가능한 일이다.



이제 그 다음줄의 코드는 이해하기가 쉽다.
make_uppercase = func.upper()
return make_uppercase

func변수에 '문자열'이 저장되어 있으니 upper()라는 메소드를 사용할 수 있게 됐다.

파이썬에서는 모든 것이 클래스다.
문자열 역시 type은 str 클래스다.

(참고)

greeting = 'say hello'
print(type(greeting))

그 결과는 str 클래스

<class 'str'>


따라서,
make_uppercase 변수에 저장된 값은
대문자로 변환된 'SAY HELLO'다.
그리고 그걸 return 하고 있다.



하지만

여기서 우리가 한가지 간과한 점이 있다.

def wrapper():
        func = function()
        make_uppercase = func.upper()
        return make_uppercase
return wrapper

아직 값을 리턴할 수가 없다.

왜일까

우리가 좀전에 다룬 것은 모두 wrapper함수 정의에 해당하는 코드였다.

wrapper함수가 호출되어야만 그 함수가 실행이 된다.

물론, 위에서 함께 분석했던 내용들이 진행되겠지만
리턴은 아직 아니다.

그렇다면 언제 호출이 되어 실행이 되는 것일까
그 답은 가장 아래의 코드에 있다.

print(say_hello())

기능이 확장된(소문자를 대문자로 데코레잇) 결과물들은
아직 say_hello함수에 저장되어 있다.

마지막에 호출이 되는 순간

say_hello() == wrapper()이기에

wrapper가 리턴한 값을 드디어 확인할 수가 있게 된다.



기존의 함수를 변화시키지 않고 데코레이터 함수를 이용하여
함수의 기능을 확장시켰다.





2. 추가 예제

def outer_decorator(original):
    def wrapper(*args, **kwargs):
        print(args)
        print(kwargs)
    return wrapper
  
@outer_decorator
def simple_func():
    print('very simple function')
    
simple_func(1, 2, 3, kite='runner', charles='American')

기존의 함수인 simple_func를 변화시켰나?
변화시키지 않았다.

기존 simple_func의 정의엔 매개변수가 없다.
그러나 호출시에 인자들이 추가된 것을 볼 수 있다.



원래 simple_func였으면 불가능한 일이었을 것이다.
아예 새로운 함수를 만들어야 했을 것이다.

데코레이터가 simple_func라는 변수를 매개변수로 받고
그 안의 wrapper함수가 호출시, 인자들을 넘겨 받은 것이다.



설명을 하는 것보다 간단한 풀이식으로 나타내 본다면

@outer_decorator
def simple_func():
    print('very simple function')

이 부분의 의미는 이렇다.
simple_func = outer_decorator(simple_func)

그리고 return wrapper 를 통해
결국 simple_func = wrapper가 되고 있다.

그래서 simple_func가 수많은 가변인자와 가변 키워드 인자들을
받을 수 있는 것이다.

simple_func를 조종하여 탈바꿈 시키는 비결은 wrapper에 있다.
그게 핵심이라고 생각한다.





3. 새로운 관점

이제 처음에 언급해던 이야기로 되돌아가자.

인스타그램에 댓글을 올리려고 한다.

http 통신의 특성은 stateless다.

즉, 클라이언트의 모든 요청에는 니가 이걸 할 자격이 있어?라는
request/response 개념이 있다.


post 함수가 있다고 하자.
그런데 post함수는 데이터베이스에 댓글을 POST하는 기능만 있다.

그렇다면, 유저가 그 계정의 주인인지, 권한은 있는지 확인하기 위해서
post함수를 새로 뜯어 고쳐야할까?

데코레이터 개념을 배웠으니 이젠 그럴 필요가 없다.
게다가 데코레이터를 사용함으로써 얻는 이득이 또한 발생한다.

간단한 레이아웃을 통해 설명해보려 한다.


def login_decorator(original_func):
    def wrapper(request, *args, **kwargs)
        try:
            (토큰 확인 : 로그인한 유저)
    	except User.DoesNotExist:
            return ~~
        except TypeError:
            return ~~
        except KeyError:
            return ~~
    return wrapper

@login_decorator
def post():
    pass
post(request, ~~)

login_decorator라는 데코레이터 함수가 이제 post함수를 더 꾸며줄거다.
그리고 그 조종의 핵심 키는 wrapper가 쥐고 있다.
wrapper를 보면 DB에 사용자가 있는지, 없을 경우(except) 리턴하는 로직이 있다.

이건 실제 얘기인데, 나는 인스타그램 계정이 없다.
그런데 내가 코멘트를 작성하려고 인스타그램 웹에서 댓글 입력란에 댓글을 쓰고 submit버튼을 눌렀다고 하자. 결과는 당연히 입력할 수가 없다.



로그인한 사람이나 그렇지 않은 나나 똑같이 submit을 누른 것은 맞다.

하지만 데코레이터는 post의 기능을 확장해준다고 배웠다.
그래서 데코레이터 함수 부분을 봤더니 안에 wrapper함수가 정의되어 있다.
(이 또한 우리가 배웠듯이, 이젠 더 이상 post함수가 더 이상 우리가 알고있던 post가 아니다)

왜냐하면 wrapper가 post를 좌지우지하는 바통을 넘겨받았기 때문이다.
따라서, post의 거동을 알기위해서는 wrapper함수의 정의를 잘 봐야 한다.

try, except 의 예외처리 구문을 통해

사용자 권한을 확인하게끔 기능을 확장해주고 있다.

또한, 예외처리 구문에서 통과하지 못하면

자연스럽게 post할 수 없게끔 디자인되었다.



0개의 댓글