2.23. 데코레이터

데코레이터 는 함수(또는 메서드)를 다른 함수로 감싸는 구문입니다. 래퍼는 원래 호출 전후에 동작을 추가하거나, 반환 값을 교체하거나, 메타데이터를 첨부할 수 있습니다. 형태는 다음과 같습니다:

@wrapper
def f():
    ...

@wrapper 줄은 f = wrapper(f) 와 동등합니다 – 함수 f 가 정상적으로 만들어진 다음 wrapper 에 전달되고, 그 결과가 이름 f 에 다시 바인딩됩니다.

왼쪽에서 함수가 "래퍼" 상자로 들어가 오른쪽에서 데코레이트된 함수로 나옵니다.

데코레이터는 함수를 받아 새 함수를 반환합니다.

2.23.1. 내장 메서드 데코레이터

몇 가지 데코레이터는 Python에 기본 포함되어 있으며 클래스 본문 안에서 사용됩니다.

2.23.1.1. @property

메서드를 계산된 속성 으로 바꿉니다. 호출자는 마치 일반 속성인 것처럼(괄호 없이) 접근하지만, 읽을 때마다 메서드가 실행됩니다:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

단순해 보이는 속성에 약간의 계산이 뒤에 필요할 때 사용하세요. 작업이 비용이 크다면 일반 메서드를 선호하세요 – 호출자는 속성 읽기가 느릴 것이라고 예상하지 않습니다.

2.23.1.2. @classmethod

인스턴스 대신 클래스 를 첫 번째 인수로 받는 메서드를 정의합니다. 첫 번째 매개변수는 관례적으로 cls 로 이름 짓습니다:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

클래스 메서드는 대체 생성자 를 제공하는 표준적인 방법입니다 – 기본이 아닌 방식으로 만들어진 인스턴스를 반환하는 팩토리 함수입니다.

2.23.1.3. @staticmethod

인스턴스도 클래스도 받지 않는 메서드를 정의합니다 – 조직적인 이유로 클래스 네임스페이스 안에 존재하는 그냥 평범한 함수입니다:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

아껴서 사용하세요. 함수가 정말로 클래스의 상태와 아무 관련이 없다면, 보통 모듈 수준의 일반 함수가 더 깔끔합니다.

2.23.2. 사용자 정의 데코레이터 작성하기

데코레이터는 함수를 받아 함수를 반환하는 함수입니다. 최소 형태:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

출력:

calling add

wrapperfunc 를 클로저로 감싸고 모든 것을 그것에 전달합니다. *args / **kwargs 덕분에 두 개의 인수를 받는 함수뿐만 아니라 어떤 함수에서도 작동합니다. 이 패턴은 더 정교한 데코레이터(타이밍, 캐싱, 실패 시 재시도)의 기초이지만 핵심은 항상 동일합니다: 함수를 받아 함수를 반환합니다.