본문 바로가기

진리는어디에/Python

[Python] 데코레이터(Decorator) #1 소개

이번 포스트 부터 파이썬의 핵심 기능 중 하나인 데코레이터(Decorator)에 대해 살펴 볼 예정이다. 데코레이터는 앞으로 나올 강좌에서도 종종 등장할 예정이므로 집중해서 읽도록 하자.

목차

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

데코레이터란?

데코레이터(Decorator)는 '장식하다', '꾸미다'라는 뜻으로 함수(또는 클래스 메소드)를 꾸며준다고 해서 이런 이름이 붙었다. 파이썬의 데코레이터는 기존 함수의 이름과 사용성을 변경하지 않는 상태에서, 함수에 추가 기능을 구현할 때 사용 되는 파이썬의 주요 특징 중에 하나다. 

데코레이터는 기존 함수를 변경하지 않으면서 함수에 기능을 추가 할 때 사용된다.

예를 들어 아래와 같은 say_hello(), say_hi() 같은 단순한 메시지를 출력 하는 함수가 있다고 가정하자.

def say_hello() :
    print('hello')

def say_hi() :
    print('hi')
    
say_hello() # hello
say_hi()    # hi

여기까지는 전혀 어렵지 않다. 그럼 이제 say_hello() 함수를 수정하지 않은 상태에서 'hello' 메시지를 출력하기 전 이모티콘을 출력하는 기능을 추가 해보자.

def add_emoticon() :
    print('= ̄ω ̄= ', end='')
    say_hello()
    
#say_hello()    
add_emoticon()   # = ̄ω ̄= hello

add_emoticon() 이라는 함수를 추가하고, 이 함수에서 이모티콘과 say_hello()를 호출하도록 만들었다. 그리고 say_hello()를 호출하는 대신 add_emoticon()을 호출 하도록 변경 되었다.

이번에는 'hi' 메시지 앞에도 이모티콘을 출력하고 싶어졌다. 하지만 현재 add_emoticon() 은 say_hello 외에는 처리 할 수 없다. add_emoticon에서 say_hello를 직접 호출하는 대신, say_hello나 say_hi 함수 객체를 인자로 받아 호출 할 수 있도록 변경 해보자.

def add_emoticon(f) :
     print('= ̄ω ̄= ', end='')
     f()
     
add_emoticon(say_hello)
add_emoticon(say_hi)

이전 강좌에서 파이썬의 함수는 일급 객체이며, 다른 함수의 인자로 전달이 가능하다는 것을 배웠다. add_emoticon() 함수는 다른 함수(say_hello, say hi)를 인자로 전달 받아, 전달 받은 함수를 다시 호출했다.

여기서 잠깐 생각해보자.
기존에 say_hello() 함수를 호출하고 있었는데, 이모티콘이 추가된 'hello' 메시지를 출력하기 위해서 say_hello() 함수를 호출 하던 부분을 모두 add_emoticon(say_hello) 함수로 변경 해야만 한다. 메시지를 출력하는 함수의 이름도 변경 되었고, 함수의 사용법(인자의 모양)도 변경 되었다.

기능을 추가하긴 했는데 코드의 변경이 많다?!

하지만 이제 부터 소개 할 '데코레이터'를 사용하면, 기존 함수의 이름과 사용법을 동일하게 유지하면서 기능을 추가하는 것이 가능하다.

그것이 데코레이터니까

기존 함수를 변경하지 않는 다는 것은
함수의 이름과 사용법을 그대로 유지하여 기존 코드를 수정하지 않다도 된다는 뜻이다.

데코레이터 동작 방식 이해

먼저, 데코레이터의 문법이나 사용법을 살펴 보기 전에,  데코레이터의 원리를 먼저 살펴 보도록하겠다. 지루 할 수 있지만 조금만 참고 따라 오길 바란다.

데코레이터는 기존 함수의 이름과 사용법을 그대로 유지하며 기능만을 추가 할 수 있게 한다고 했다. 이제 부터는 파이썬에서 어떻게 그것이 가능한지 원리에 대해 살펴 보도록 하자.

def add_emoticon(func) :

    def inner() :
         print('= ̄ω ̄= ', end='')
         func()
    
    return inner
    
def say_hello() :
    print('hello')
    
f = add_emoticon(say_hello)
f()  # f는 결국 inner() 함수이며 = ̄ω ̄= hello를 출력한다

함수 내부에서 다른 함수를 정의 할 수 있는 파이썬의 기능을 이용해,  add_emoticon() 함수의 기존 내용 대신에, 내부 함수 inner()를 정의하고 그 내부 함수에서 이모티콘과 메시지를 출력하는 기능을 구현했다.

이제 add_emoticon() 함수를 실행하면 hello 메시지를 출력 하는 것이 아니라 내부 함수 객체를 리턴한다.

12라인에서는 add_emoticon() 함수가 리턴하는 inner함수 객체를 변수 f에 저장하고, 13라인에서 f()를 호출한다. f()함수의 호출 결과는 이전 예제의 add_emoticon() 함수와 동일하다.

여기서 잠깐 이전 포스트에서 배운 [함수 객체]를 떠올려 보자. 파이썬에서 함수의 이름은 단지 함수 객체를 가리키는 변수일 뿐이라고 했다. 물론 변수이므로 다른 값을 가지는 것도 가능하다.

그렇다면 위 예제에서 변수 'f'를 'say_hello'로 변경하면 어떻게 될까?

say_hello = add_emoticon(say_hello)
say_hello()  # f는 결국 inner() 함수이며 = ̄ω ̄= hello를 출력한다

파이썬의 특성상 함수의 이름이라고 하더라도 변수일 뿐이므로 다른 함수 객체를 대입하는 것도 가능하다. 위 예제에서  say_hello는 이제부터 inner() 함수를 가리키는 변수가 되었다.

파이썬에선 함수 이름도 변수다.

위와 같은 방법을 이용하면, say_hello = add_emoticon(say_hello) 코드 한 줄 추가로 기존 함수의 수정, 호출하는 이름의 변경, 그리고 사용법, 이 모든 것들의 변경 없이 없이 이모티콘 출력 기능이 추가된 say_hello 함수를 사용 할 수 있게 되었다.

NOTE - 위에서 설명의 편의상 "함수의 이름은 변수일 뿐이다"라고 했지만 정확하게는 틀린말이다. 함수를 정의하면 함수의 이름과 같은 심볼이 테이블에 생성된다. 그런데 변수도 심볼 테이블에 저장 되므로 함수 이름과 같은 변수를 정의하면 심볼 테이블에 값이 바뀌게 되는 것이다. 자세한 사항은 [여기]를 살펴보자.

@로 데코레이터 사용하기

위에서 데코레이터의 원리는 데코레이터 함수를 작성하고, 원래 호출 해야하는 함수의 이름에 데코레이터 함수를 대신 할당하는 것임을 알았다. 그런데 매번 'say_hello = add_emoticon(say_hello)' 같이 작성하려니 은근 귀찮고 가독성도 떨어진다.

그럼 이제 간단히 @를 이용하여 데코레이터를 사용하는 방법을 알아 보도록 하자.

def add_emoticon(func) :

    def inner() :
         print('= ̄ω ̄= ', end='')
         func()
    
    return inner
    
def say_hello() :
    print('hello')
    
say_hello = add_emoticon(say_hello)
say_hello()  #  = ̄ω ̄= hello

@add_emoticon    # say_hi = add_emoticon(say_hi)
def say_hi() :
    print('hi')

15라인에서 say_hi 함수 앞에 @add_emoticon를 정의해주었다. 파이썬 인터프리터는 '@함수_이름'과 같은 데코레이터 표현식을 정의한 함수를 만나면 내부적으로 이 코드를 'say_hi = add_emoticon(say_hi)' 과 같이 변경한다.

'say_hi = add_emoticon(say_hi)'는 @add_emoticon으로 간단히 쓸 수 있다.

데코레이터 규칙

위 데코레이터 동작 방식을 토대로 데코레이터를 사용하기 위한 규칙을 알아보도록 하자.

def add_emoticon(func) :

    def inner() :
         print('= ̄ω ̄= ', end='')
         func()
    
    return inner
    
@add_emoticon    # say_hi = add_emoticon(say_hi) 와 동일
def say_hi() :
    print('hi')
    
say_hi()

규칙 1. 데코레이터를 사용하기 위해서는 데코레이터 함수가 필요하다.

  • 데코레이터 함수는 꾸며줄 대상 함수를 인자로 받을 수 있어야 한다.
  • 데코레이터 함수는 꾸미는 로직을 담은 내부 함수를 리턴해야 한다.
  • 내부 함수는 꾸며줄 대상 함수를 호출 해야 한다.

규칙 2. 꾸며줄 대상 함수를 정의 앞에 '@데코레이터함수_이름' 표현식이 있어야 한다.

생각 보다 간단한 규칙이다. 더구나 위에서 데코레이터의 동작 원리까지 이해하고 온 상황이라 이 규칙들이 더욱 쉽게 느껴질 것이다.

마치며

이상으로 데코레이터의 기본 문법과 내부 동작 원리에 대해서 살펴 보았다. 하지만 아직 완벽한 데코레이터는 아니다.  다음 강좌에서는 인자를 가지는 함수를 데코레이팅 할 때 발생하는 문제점과 그 해결 방법들을 살펴 보도록 하겠다.

부록 1. 같이 보면 좋은 글

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