이 글의 작성자는 C/C++ 프로그래밍을 하던 사람이다. 이 글은 Python을 복습하며 작성하는 글이니, 부족한 부분이 있으면 얼마든지 피드백을 주시기 바란다.

 

기존 함수에 추가적인 기능을 더하기

다음과 같이 더하기를 수행하는 함수가 있다고 생각해보자.

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

그리고 당신은 이 함수가 호출 될 때의 상황을 기록하고 싶다고 해 보자. 예를 들어 plus 함수를 호출한다면, plus 함수를 실행합니다 라고 출력하고 싶다. 그러면 어떻게 해야 할까?

간단하다. 그냥 plus 함수를 고치면 된다.

def plus(a,b):
    print('plus 함수를 실행했습니다')
    return a+b

이러한 기록을 plus 함수 하나에만 하겠다면 딱히 상관은 없을 수도 있다. 하지만 기록하고 싶은 함수가 하나하나 늘어난다고 생각해보자. minus 함수에도 추가하고 싶고, multiply 함수에도 추가하고 싶고…

그런데 추가해야 할 코드를 생각해보면, 함수 이름 부분만 바꾸면 될 것 같다. minus 함수를 실행합니다, multiply 함수를 실행합니다 식으로 말이다.

이럴 때 사용할 수 있는 것이 바로 데코레이터(decorator)이다.

 

데코레이터(decorator)

데코레이터는 이렇게 만들면 된다. 함수와 변수 이름은 똑같이 쓰지 않아도 무방하다.

def outer(func):
    def inner(*args, **kwargs):
        # func를 실행하기 전에 할 동작
        result = func(*args, **kwargs)
        # func를 실행한 후에 할 동작
        return result
    return inner

어째서 이런 코드가 만들어졌을까? 차근차근 생각해보자.

 

  • outer 함수가 하는 일은 무엇인가

outer 함수는 하나의 인자 func을 받아들여서, 내부 함수인 inner를 반환한다. 대충 보니 inner 함수에서 func를 쓰는 것이 보인다. 즉, 클로저를 만들어내고 있다.

 

  • 그럼 inner 함수가 하는 일은 무엇인가

위치 인자와 키워드 인자를 받아들이고, outer 함수의 인자인 func를 호출하며, 호출한 결과인 result를 반환한다. 그리고 func함수를 호출하기 이전/이후에 아무 코드나 추가할 수 있는 것으로 보인다.

 

  • 그러면 이 함수에 plus 함수 객체를 넣으면 어떻게 될까

한번 넣어보자.

def outer(func):
    def inner(*args, **kwargs):
        result = func(*args, **kwargs)
        return result
    return inner

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

a = outer(plus)
print(a)

위의 코드를 실행하면 다음과 비슷하게 나올 것이다. (뒤의 숫자들이 좀 다르게 나올 것이다)

<function outer.<locals>.inner at 0x01B76580>

outer 함수의 클로저 함수인 inner를 돌려받은 것을 확인할 수 있을 것이다. 그러면 inner 함수의 동작이 무엇이었는가? 위치 인자/키워드 인자를 받아서 outer의 인자로 받은 func를 실행하는게 다였다. 원래 plus 함수는 2개의 위치 인자를 받아서 합을 돌려주는 일을 하고 있었다. 그러면 정수 2개를 전달하면 될 것이다.

그러면 outer 함수의 결과로 받은 a는 위치 인자 2개를 전달하면 그 결과로 두 인자의 합을 돌려받을 수 있을 것이다.

print(a(2,3))

위의 코드를 실행하면 2+3의 결과인 5가 나오는 것을 확인할 수 있을 것이다.

 

  • inner 함수에 코드를 추가한다면

지금의 inner 함수는 단순히 outer에서 받아들인 함수 func를 그대로 호출하는 함수이다. 여기서 코드를 추가한다면? func 함수의 호출과 더불어 또 다른 행동을 추가할 수 있을 것이다. 한번 이렇게 코드를 수정해보자.

def outer(func):
    def inner(*args, **kwargs):
        print(func.__name__,'함수를 실행했습니다')
        result = func(*args, **kwargs)
        return result
    return inner

위의 함수에서 무엇이 추가되었는가? 함수 이름을 출력하는 코드가 추가되었다. func.__name__이라는 익숙하지 않은 것이 보일텐데, 함수 객체는 함수의 이름을 위와 같은 이름으로 가지고 있다.

그러면 다시 한번 다음 코드를 실행해보자.

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

a = outer(plus)
print(a(2,3))

그러면 다음과 같이 출력되는 것을 확인할 수 있을 것이다.

plus 함수를 실행했습니다
5

새롭게 탈바꿈한 plus 함수는 이제 자기 자신이 호출되고 있다는 로그를 남기게 되었다! 그러면 minus 함수, multiply 함수도 만들어서 똑같이 해 보자.

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

def minus(a,b):
    return a-b

def multiply(a,b):
    return a*b

a = outer(plus)
print(a(2,3))

b = outer(minus)
print(b(2,3))

c = outer(multiply)
print(c(2,3))

그러면 다음과 같은 결과를 확인할 수 있을 것이다.

plus 함수를 실행했습니다
5
minus 함수를 실행했습니다
-1
multiply 함수를 실행했습니다
6

이제 outer에 함수를 집어넣으면, 원본 함수 기능에 로그를 남기는 기능이 추가된다!

 

데코레이터 더 편하게 쓰기

여기서 데코레이터를 더 편하게 쓰는 방법이 있다. 아래와 같이 사용하면 된다.

@outer
def plus(a,b):
    return a+b

데코레이터를 적용하고 싶은 함수 정의 바로 위에 @를 붙이고, 데코레이터를 수행하는 함수의 이름을 붙이면 된다! 그러면 이제 plus 함수는 outer 함수가 생성한 데코레이션 함수인 inner가 된다!

print(plus(2,3))

위의 코드를 실행하면 다음과 같이 출력되는 것을 확인할 수 있을 것이다.

plus 함수를 실행했습니다
5

정말 놀랍도록 간결해진 것을 확인할 수 있을 것이다. 방금 적용했던 minus, multiply 함수도 이렇게 적용하면 된다.

 

@를 붙여서 적용한 코드는 이 코드와 똑같다고 보면 된다.

def plus(a,b):
    return a+b
plus = outer(plus)

즉, 함수 자기 자신을 @를 붙인 함수의 인자로 넘겨서 얻은 결과 값을 자기 자신에게 다시 대입하는 것과 같은 동작을 취한다. 실제로 plus의 이름을 출력해보면 inner가 나올 것이다.

@outer
def plus(a,b):
    return a+b

print(plus.__name__)

 

데코레이터는 말 그대로 함수를 꾸며서 특정 함수에게 추가적인 기능을 제공하는 역할을 수행할 수 있다. 위와 같이 함수 호출할 때 로그를 남기는 기능을 추가할 수도 있고, 함수 실행 성능을 측정하는 기능도 추가할 수 있다.

나중에 다른 오픈 소스 코드들을 보다 보면 생각하지도 못한 방법으로 쓰는 경우도 볼 수 있을 것이다.

 

Categories:

Updated:

Comments