-
데코레이터(장식자)Python 2023. 10. 25. 13:50
장식하는 도구
클래스에서 메서드를 만들 때 @staticmethod, @classmethod, @abstractmethod 등을 붙였는데 @로 시작하는 것들이 데코레이터
> 함수(메서드)를 장식
데코레이터 만들기
데코레이터는 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용
예를 들어 지금까지는 시작과 끝을 출력하고 싶다면 함수 시작, 끝 부분에 print를 넣어야 함 > 번거로움
def hello(): print('hello 함수 시작') print('hello') print('hello 함수 끝') def world(): print('world 함수 시작') print('world') print('world 함수 끝') hello() world() # 실행 결과 hello 함수 시작 hello hello 함수 끝 world 함수 시작 world world 함수 끝
이런 경우 데코레이터 사용
def trace(func): # 호출할 함수를 매개변수로 받음 def wrapper(): # 호출할 함수를 감싸는 함수 print(func.__name__, '함수 시작') # __name__으로 함수 이름 출력 func() # 매개변수로 받은 함수 호출 print(func.__name__, '함수 끝') return wrapper # wrapper 함수 반환 def hello(): print('hello') def world(): print('world') trace_hello = trace(hello) # 데코레이터에 호출할 함수를 넣음 trace_hello() # 반환된 함수를 호출 trace_world = trace(world) trace_world() # 실행 결과 hello 함수 시작 hello hello 함수 끝 world 함수 시작 world world 함수 끝
데코레이터 trace는 호출할 함수를 매개변수로 받음(trace는 추적하다란 뜻, 프로그래밍에서 함수의 실행 상황을 추적할 때 trace 사용)
wrapper는 함수를 감싼다는 뜻에서 사용했는데, 다른 이름 사용해도 상관없음
매개변수로 받은 함수의 원래 이름 출력할 때 __name__ 속성 활용
> 함수 안에서 함수를 만들고 반환하는 클로저
@으로 데코레이터 사용
# @데코레이터 # def 함수이름(): # 코드 def trace(func): # 호출할 함수를 매개변수로 받음 def wrapper(): print(func.__name__, '함수 시작') # __name__으로 함수 이름 출력 func() # 매개변수로 받은 함수 호출 print(func.__name__, '함수 끝') return wrapper # wrapper 함수 반환 @trace # @데코레이터 def hello(): print('hello') @trace def world(): print('world') hello() world() # 실행 결과 hello 함수 시작 hello hello 함수 끝 world 함수 시작 world world 함수 끝
데코레이터는 함수를 감싸는 형태로 구성되어 있어 기존 함수를 수정하지 않으면서 추가 기능을 구현할 때 사용
데코레이터 여러 개 지정
# @데코레이터1 # @데코레이터2 # def 함수이름(): # 코드 def decorator1(func): def wrapper(): print('decorator1') func() return wrapper def decorator2(func): def wrapper(): print('decorator2') func() return wrapper @decorator1 @decorator2 def hello(): print('hello') hello() # 실행 결과 decorator1 decorator2 hello
@를 사용하지 않았을 때는 이 코드와 동작이 같음
decorated_hello = decorator1(decorator2(hello)) decorated_hello()
매개변수와 반환값을 처리하는 데코레이터 만들기
def trace(func): # 호출할 함수를 매개변수로 받음 def wrapper(a, b): # 호출할 함수 add(a, b)의 매개변수와 똑같이 지정 r = func(a, b) # func에 매개변수 a, b 넣어 호출하고 반환값 변수에 저장 print('{0}(a={1}, b={2}) -> {3}'.format(func.__name__, a, b, r)) # 매개변수와 반환값 출력 return r # func의 반환값을 반환 return wrapper # wrapper 함수 반환 @trace # @데코레이터 def add(a, b): # 매개변수 두 개 return a + b print(add(10, 20)) # 실행 결과 add(a=10, b=20) -> 30 30
wrapper 함수에서 func의 반환값을 반환하지 않으면 add 함수를 호출해도 반환값이 나오지 않으므로 주의할 것
wrapper 함수에서 func의 반환값을 출력할 필요 없다면 return func(a, b)처럼 func를 호출해 바로 반환해도 됨
가변 인수 함수 데코레이터
def trace(func): def wrapper(*args, **kwargs): # 가변 인수 함수로 만듦 r = func(*args, **kwargs) # func에 args, kwargs를 언패킹해 넣어 줌 print('{0}(args={1}, kwargs={2}) -> {3}'.format(func.__name__, args, kwargs, r)) # 매개변수와 반환값 출력 return r # func의 반환값을 반환 return wrapper # wrapper 함수 반환 @trace # @데코레이터 def get_max(*args): # 위치 인수를 사용하는 가변 인수 함수 return max(args) @trace # @데코레이터 def get_min(**kwargs): # 키워드 인수를 사용하는 가변 인수 함수 return min(kwargs.values()) print(get_max(10, 20)) print(get_min(x=10, y=20, z=30)) # 실행 결과 get_max(args=(10, 20), kwargs={}) -> 20 20 get_min(args=(), kwargs={'x': 10, 'y': 20, 'z': 30}) -> 10 10
매개변수가 있는 데코레이터
def is_multiple(x): # 데코레이터가 사용할 매개변수 지정 def real_decorator(func): # 호출할 함수를 매개변수로 받음 def wrapper(a, b): # 호출할 함수의 매개변수와 똑같이 지정 r = func(a, b) # func 호출, 반환값 변수에 저장 if r % x == 0: print('{0}의 반환값은 {1}의 배수'.format(func.__name__, x)) else: print('{0}의 반환값은 {1}의 배수가 아님'.format(func.__name__, x)) return r # func의 반환값 반환 return wrapper return real_decorator @is_multiple(3) # @데코레이터(인수) def add(a, b): return a + b print(add(10, 20)) print(add(2, 5)) # 실행 결과 add의 반환값은 3의 배수 30 add의 반환값은 3의 배수가 아님 7
앞에서는 데코레이터 만들 때 함수 안에 함수를 하나만 만들었지만, 매개변수 있는 데코레이터를 만들 때는 함수를 하나 더 만들어야 함
1. is_multiple 함수를 만들고 데코레이터가 사용할 매개변수 x 지정
2. is_multiple 함수 안에서 실제 데코레이터 역할을 하는 real_decorator 만듦
> 이 함수에서 호출할 함수를 매개변수로 받음
3. real_decorator 함수 안에서 wrapper 함수 만듦
데코레이터 사용할 때는
@데코레이터(인수) def 함수이름(): 코드
클래스로 데코레이터 만들기
클래스를 활용할 때는 인스턴스를 함수처럼 호출하게 해 주는 __call__ 메서드 구현
class Trace: def __init__(self, func): # 호출할 함수를 인스턴스의 초깃값으로 받음 self.func = func # 호출할 함수를 속성 func에 저장 def __call__(self): print(self.func.__name__, '함수 시작') self.func() print(self.func.__name__, '함수 끝') @Trace def hello(): print('hello') hello() # 실행 결과 hello 함수 시작 hello hello 함수 끝
↑ 함수의 시작과 끝을 출력하는 데코레이터
클로저 형태의 데코레이터와 사용하는 방법이 같음
@을 지정하지 않고 데코레이터의 반환값을 호출하는 방식으로도 사용할 수 있음
def hello(): # @데코레이터를 지정하지 않음 print('hello') trace_hello = Trace(hello) # 데코레이터에 호출할 함수를 넣어 인스턴스 생성 trace_hello() # 인스턴스를 호출 .__call__ 메서드가 호출됨
클래스로 매개변수와 반환값을 처리하는 데코레이터 만들기
class Trace: def __init__(self, func): # 호출할 함수를 인스턴스의 초깃값으로 받음 self.func = func # 호출할 함수를 속성 func에 저장 def __call__(self, *args, **kwargs): # 호출할 함수의 매개변수를 처리 r = self.func(*args, **kwargs) # self.func에 매개변수를 넣어서 호출 # 반환값 변수에 저장 print('{0}(args={1}, kwargs={2}) -> {3}'.format(self.func.__name__, args, kwargs, r)) # 매개변수와 반환값 출력 return r # self.func의 반환값 반환 @Trace def add(a, b): return a + b print(add(10, 20)) print(add(a=10, b=20)) # 실행 결과 add(args=(10, 20), kwargs={}) -> 30 30 add(args=(), kwargs={'a': 10, 'b': 20}) -> 30 30
클래스로 매개변수가 있는 데코레이터 만들기
class IsMultiple: def __init__(self, x): # 데코레이터가 사용할 매개변수를 초깃값으로 받음 self.x = x # 매개변수를 속성 x에 저장(__call__ 메서드에서 사용할 수 있도록) def __call__(self, func): # 호출할 함수를 매개변수로 받음 def wrapper(a, b): # 호출할 함수의 매개변수와 똑같이 지정 r = func(a, b) if r % self.x == 0: print('{}의 반환값은 {}의 배수'.format(func.__name__, self.x)) else: print('{}의 반환값은 {}의 배수가 아님'.format(func.__name__, self.x)) return r return wrapper @IsMultiple(3) def add(a, b): return a + b print(add(10, 20)) print(add(2, 5)) # 실행 결과 add의 반환값은 3의 배수 30 add의 반환값은 3의 배수가 아님 7
'Python' 카테고리의 다른 글
모듈과 패키지 사용 (0) 2023.10.30 정규표현식 (0) 2023.10.25 코루틴 (0) 2023.10.24 제너레이터(발생자) (0) 2023.10.24 이터레이터(반복자) (0) 2023.10.24