본문 바로가기

Develop/Python

Python function Decorator (함수 장식자 @)

https://jythonbook-ko.readthedocs.io/en/latest/DefiningFunctionsandUsingBuilt-Ins.html#id20

 

4장. 함수 정의하기 및 내장함수 사용하기 — 자이썬(Jython) 완벽 안내서

함수는 파이썬에서 작업의 기본 단위이다. 파이썬에서의 함수는 작업을 수행하여 결과를 반환한다. 이 장에서는 함수의 기초에서 시작하여 내장(built-in) 함수의 사용법을 알아볼 것이다. 내장

jythonbook-ko.readthedocs.io

 

장식자 decorator는 함수를 변형시키는 방법을 기술하는 데에 편리하다. 그것들은 본질적으로, 그것들이 장식하는 함수의 행위를 강화하는 메타 프로그래밍 기법이다. 함수 장식자란, 이미 정의된 함수를 다른 함수를 장식하는 데에 사용하는 것으로, 장식자를 프로그램하는 것은 기본적으로 장식된 함수가 그 함수로 전달될 수 있도록 해준다. 간단한 예를 살펴보자.

예제 4-33.

def plus_five(func):
    x = func()
    return x + 5

@plus_five
def add_nums():
    return 1 + 2

이 예제에서는 add_nums() 함수는 plus_five() 함수로 장식되어 있다. 이것은 plus_five 함수로 add_nums 함수를 전달하는 것과 같은 효과가 있다. 달리 말해, 이러한 기법을 사용하기 쉽게 만든 문법적인 감초와 같은 것이 바로 장식자이다. 다음의 코드는 위에서 살펴본 장식과 동일한 기능을 한다.

예제 4-34.

add_nums = plus_five(add_nums)

사실 이렇게 하면 add_nums는 더이상 함수가 아니며, 정수가 되어버린다. plus_five 로 장식을 하면 더이상 add_nums()를 호출 할수는 없고 오로지 정수로서 참조만 할수 있다. 위에서 본것과 같이 add_nums 는 import 시점에 plus_five로 전달 된다. 보통 우리는 add_nums를 계속 호출 가능한 함수로 가지고 싶을 것이다. 이 예제를 좀더 쓸만하게 만들기 위해서는, add_nums를 다시 호출 가능하도록 만들고, 추가되는 숫자를 변경할 수 있는 능력을 부여하면 좋을 것이다. 그렇게 하려면, 장식된 함수로부터 인자를 취하는 안쪽 함수를 포함하도록 장식 함수를 약간 손볼 필요가 있다.

예제 4-35.

def plus_five(func):
    def inner(*args, **kwargs):
        x = func(*args, **kwargs) + 5
        return x
    return inner

@plus_five
def add_nums(num1, num2):
    return num1 + num2

이제 우리는 다시 한번 add_nums() 함수를 호출할 수 있으며, 우리는 또한 그것에 두 개의 인수를 전달할 수 있다. 왜냐하면 그것은 전달되는 plus_five 함수로 장식된 후에 두 개의 인자가 합산되어 숫자 5가 그 합계에 더해질 것이기 때문이다. 그런 다음 결과가 반환된다.

예제 4-36.

>>> add_nums(2,3)
10
>>> add_nums(2,6)
13

함수 장식자의 기초를 다루었으니 이제 개념을 이해하기 위한 깊이 있는 예제를 살펴볼 차례이다. 다음 장식자 함수 예제에서 우리는 이전에 만든 tip_calculator 함수와 판매세금 계산을 추가하는 상상치 못한 방법을 보게 될것이다. 알다시피 원래의 calc_bill 함수는 금액들의 나열, 그러니까 청구서의 각 항목들의 가격을 취한다. calc_bill 함수는 단순히 금액을 합하여 값을 반환한다. 예제에서는, sales_tax 장식자를 함수에 적용함으로써, 모든 금액에 대한 합계를 계산하여 반환할 뿐만 아니라 표준 세율을 적용하고 세 금액과 총 금액도 반환하도록 함수를 변형한다.

예제 4-37.

def sales_tax(func):
    ''' Applies a sales tax to a given bill calculator '''
    def calc_tax(*args, **kwargs):
        f = func(*args, **kwargs)
        tax = f * .18
        print "Total before tax: $ %.2f" % (f)
        print "Tax Amount: $ %.2f" % (tax)
        print "Total bill: $ %.2f" % (f + tax)
    return calc_tax

@sales_tax
def calc_bill(amounts):
    ''' Takes a sequence of amounts and returns sum '''
    return sum(amounts)

장식자 함수는 안쪽 함수를 포함하며, 안쪽 함수는 두 개의 인자 - 매개변수의 시퀀스와 키워드 args의 사전 - 를 받는다. 우리는 원래의 함수로 전달되는 인자뿐만 아니라 장식자 함수 내에 적용되도록 장식에서 호출했을 때 우리는 원래의 함수에 해당 인수를 전달해야 한다. 이 경우, 우리는 금액의 시퀀스를 calc_bill에 전달하고자 하며, *args와 **kwargs 인자를 함수에 전달함으로써 금액 시퀀스가 장식자를 통하여 전달되었음을 보장할 수 있다. 장식자 함수는 다음과 세금 합계 금액에 대한 간단한 계산을 수행하고 결과를 출력한다. 실제 예제를 보자.

예제 4-38.

>>> amounts = [12.95,14.57,9.96]
>>> calc_bill(amounts)
Total before tax: $ 37.48
Tax Amount: $ 6.75
Total bill: $ 44.23

장식을 하고 있을 때에 장식자 함수에 인자를 전달하는 것도 가능하다. 그렇게 하려면, 장식자 함수 내에 다른 함수를 중첩시켜야 한다. 바깥쪽 함수는 장식자 함수로 전달되는 매개변수를 취하고, 안쪽 함수는 장식된 함수를 취하며, 가장 안쪽의 함수가 실제로 일을 한다. 또 다른 스핀을 팁 계산기 예제에서 하고, 팁 계산을 calc_bill 함수에 적용할 장식자를 생성한다.

예제 4-39.

def tip_amount(tip_pct):
    def calc_tip_wrapper(func):
        def calc_tip_impl(*args, **kwargs):
            f = func(*args, **kwargs)
            print "Total bill before tip: $ %.2f" % (f)
            print "Tip amount: $ %.2f" % (f * tip_pct)
            print "Total with tip: $ %.2f" % (f + (f * tip_pct))
        return calc_tip_impl
    return calc_tip_wrapper

이제 장식자 함수가 동작하는 것을 살펴보자. 백분율 금액을 장식에 전달하면 그것이 장식자 함수에 적용됨을 알게 될 것이다.

예제 4-40.

>>> @tip_amount(.18)
... def calc_bill(amounts):
...     ''' Takes a sequence of amounts and returns sum '''
...     return sum(amounts)
...
>>> amounts = [20.95, 3.25, 10.75]
>>> calc_bill(amounts)
Total bill before tip: $ 34.95
Tip amount: $ 6.29
Total with tip: $ 41.24

팁 백분율을 구분할 수 있다는 점을 제외하면, 장식자를 통하여 판매 세금 계산기에서와 비슷한 결과를 얻었다. 목록의 모든 금액이 합산된 후에 팁이 적용된다. 장식자 @ 구문을 사용하지 않을 경우 어떻게 되는지 간단히 살펴보자.

예제 4-41.

calc_bill = tip_amount(.18)(calc_bill)

import 시에, tip_amount() 함수는 팁의 백분율과 calc_bill 함수를 둘 다를 매개변수로 취하여, 그 결과는 새로운 calc_bill 함수가 된다. 장식자를 포함함으로써, 실제로는 tip_amount(.18)에서 반환한 함수를 가지고 calc_bill을 장식한다. 좀 더 크게 보아, 이러한 장식자 해법을 완전한 애플리케이션에 적용한다면, 키보드 입력으로 팁 백분율을 받아서 그것을 예제에서 보인 것처럼 장식자에 전달할 수 있을 것이다. 팁 금액은 상황에 따라 변동될 수 있는 변수가 될 것이다. 끝으로, 보다 복잡한 장식자 함수를 다룬다면, 본래 장식된 함수를 전혀 건드리지 않고도 함수 내의 동작을 변경할 수 있을 것이다. 장식자는 우리의 코드를 더욱 융통성 있고 관리하기 쉽게 해주는 쉬운 방법이다.