파이썬과 데코레이터
이 글의 작성자는 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__)
데코레이터는 말 그대로 함수를 꾸며서 특정 함수에게 추가적인 기능을 제공하는 역할을 수행할 수 있다. 위와 같이 함수 호출할 때 로그를 남기는 기능을 추가할 수도 있고, 함수 실행 성능을 측정하는 기능도 추가할 수 있다.
나중에 다른 오픈 소스 코드들을 보다 보면 생각하지도 못한 방법으로 쓰는 경우도 볼 수 있을 것이다.
Comments