본문 바로가기

진리는어디에/Python

[Python] 데코레이터(Decorator) #3 인자를 가지는 데코레이터

이전 강좌에서 functools 모듈의 wraps 데코레이터를 사용하면서 지금까지 우리가 배웠던 데코레이터와는 다르게 인자를 사용하고 있는 것을 보았다. 이번 강좌에서는 이와 같이 인자를 받을 수 있는 데코레이터에 대해 알아 보도록 하겠다.

목차

  1. 파이썬 데코레이터 소개
  2. 함수의 인자와 리턴값 처리하기
  3. >> 인자를 가지는 데코레이터
  4. 데코레이터 활용

인자를 가지는 데코레이터

지금까지 우리가 만든 add_emoticon 예제는 한가지 이모티콘 밖에 출력 할 수 없었다. 이제 아래 예제 처럼 add_emoticon 함수가 인자를 가질 수 있도록 개선하여 고정된 이모티콘이 아닌 인자로 넘겨 받은 문자열을 출력 할 수 있도록 해보자.

# @add_emoticon               # say_hello = add_emoticon(say_hello)
@add_emoticon('^0^')          # say_hello = add_emoticon('^0^')(say_hello)
def say_hello(name) :
    print(f'hello {name}!')

기존 @add_emoticon을 사용하면 인터프리터가 say_hello = add_emoticon(say_hello)와 같은 코드로 변경해준다고 이야기 했다. 똑같은 방식으로 데코레이터에 @add_emoticon('^0^')와 같이 인자를 추가해주면 컴파일러는 기존 코드에 저 인자 부분을 추가한 say_hello = add_emoticon('^0^')(say_hello)와 같은 코드로 변경한다.

그런데 코드 모양이 이상해 보인다? 함수를 호출하는데 괄호를 여러번 여닫고 있다. 문법 오류 같아 보이긴 하지만 정상적인 코드 맞다. 여러분의 이해를 돕기위해 위 코드를 풀어서 보도록 하겠다.

# say_hello = add_emoticon('^0^')(say_hello)
decorator = add_emoticon('^0^')
say_hello = decorator(say_hello)

add_emoticon에서 기존 say_hello 대신 이모티콘을 인자로 받아 기존 add_emoticon 과 동일한 역할을 해주는 어떤 함수 객체(여기서는 decorator)를 생성하고, 그 함수 객체를 이용해 기존 add_emoticon이 했던것과 동일한 작업을 처리하고 있다.

위 내용을 토대로 add_emoticon 함수를 하나씩 하나씩 변경해 보도록 하자.

# def add_emoticon(func) :
def add_emoticon(emoticon) : # 기존 함수 객체 대신 이모티콘을 인자로 받도록 수정

    @wraps(func)
    def inner(*args, **kwargs) :
         print('= ̄ω ̄= ', end='')
         result = func(*args, **kwargs)
         return result
    return inner

기존 함수 객체 대신 이모티콘을 인자로 받을 수 있도록 수정했다. 그럼 이제는 기존 add_emoticon과 동일한 역할을 하는 어떤 함수 객체를 리턴해야 할 차례다. 아래 예제에서는 decorator라는 함수객체를 만들어 리턴했다.

def add_emoticon(emoticon) :

    def decorator(func) : # 기존 add_emoticon과 동일한 역할을 하는 함수 객체
    
        @wraps(func)
        def inner(*args, **kwargs) :
            print('= ̄ω ̄= ', end='')
            result = func(*args, **kwargs)
            return result
        return inner
        
    return decorator

이제 고정된 이모티콘 대신 데코레이터의 인자로 넘겨진 이모티콘을 대신 출력 할 수 있도록 변경해보자. 하는 김에 인자가 없는 경우 기본 이모티콘을 출력 할 수 있도록 emoticon에 디폴트 값도 추가하자.

def add_emoticon(emoticon = '^_^') : # emoticon에 디폴트 인자 추가

    def decorator(func) :
    
        @wraps(func)
        def inner(*args, **kwargs) :
            print(emoticon, ' ', end='') # 고정 이모티콘 대신 인자로 넘어온 이모티콘 출력
            result = func(*args, **kwargs)
            return result
        return inner
        
    return decorator

축하한다. 이제 인자를 가지는 데코레이터가 완성 되었다. 이제 다시 처음으로 돌아가 say_hello와 say_hi 함수에 데코레이터를 적용해보자.

from functools import wraps

def add_emoticon(emoticon='^_^') :

    def decorator(func) :
    
        @wraps(func)
        def inner(*args, **kwargs) :
            print(emoticon, ' ', end='')
            result = func(*args, **kwargs)
            return result
        return inner
        
    return decorator
    
@add_emoticon('^o^')   # 커스텀 이모티콘을 사용한 hello
def say_hello(name) :
    print(f'hello {name}!')
    
@add_emoticon()        # 디폴트 이모티콘을 사용한 hi
def say_hi() :
    print('hi')

say_hello('jason')     # ^o^  hello jason!
say_hi()               # ^_^  hi

위 예제를 실행하면 데코레이터의 인자에 따라 이모티콘을 출력하는 hello와 hi 메시지를 볼 수 있다.

마치며

이상으로 데코레이터에 인자를 사용 할 수 있는 방법에 대해 살펴 보았다. 본 포스트에서는 설명을 위해 극히 간단한 예제를 사용했지만, 데코레이터에 인자를 사용한다는 복잡 다양한 상황에서 별도의 데코레이터들을 계속 만들지 않아도 되게 해주는 강력한 기능이므로 꼭 숙지하고 넘어 가도록 하자. 

다음 강좌 데코레이터의 활용에서는 데코레이터 강좌 시리즈의 마지막으로써 지금까지 배웠던 것들을 종합적으로 사용해 보는 시간을 갖도록 하겠다.

부록 1. 같이 보면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!