[Python] 파이썬 Decorator 데코레이터 사용하기

isitcake_yes·2022년 10월 7일
post-thumbnail

1. 데코레이터 이해하기

  • 함수를 수정하지 않은 상태에서, 추가기능을 구현할 때 사용한다.
  • 함수 앞에 @데코레이터 를 붙여서 사용한다.
  • 함수(메서드)를 장식한다는 뜻에서 이름이 붙여졌다.
  • decorator 함수를 만들때 내부 함수를 wrapper 라고 한다.

> decorator 사용 전

def hello():
    print('hello 함수 시작')
    print('hello')
    print('hello 함수 끝')
def world():
    print('world 함수 시작')
    print('world')
    print('world 함수 끝')
hello()
world() 
#결과
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝

> decorator (trace) 만들기

def trace(func):   # 호출할 함수를 매개변수로 받음
    def wrapper():  # 호출할 함수를 감싸는 함수
        print(func.__name__, '함수 시작')  # __name__으로 함수 이름 출력
        func()   # 매개변수로 받은 함수를 호출
        print(func.__name__, '함수 끝')
        print('*****')
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 함수 끝
*****

> decorator (@trace) 적용하기

# 데코레이터 적용하기
@trace
def hello():
	print('hello')
@trace
def world():
	print('world')
# 함수 그대로 호출
hello()
world()
# 결과
hello 함수 시작
hello
hello 함수 끝
*****
world 함수 시작
world
world 함수 끝
*****

2. 데코레이터 사용하기

: 매개변수와 반환값을 처리하는 데코레이터

* 매개변수 함수 데코레이터

  • def wrapper(a, b), add(a, b)와 같이 호출할 함수의 매개변수와 똑같이 지정한다.
  • 매개변수가 있는 데코레이터는 값을 지정해서 동작을 바꿀 수 있다.

> example 1

def trace(func):  # 호출할 함수를 받음
	def wrapper(a, b):   # 호출할 함수의 매개변수와 똑같이 지정 ex) add(a, b)
    	r = func(a,b)    # 반환값을 r변수에 저장
        # 매개변수와 반환값 출력
        pring('{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

> example 2

  • is_multiple(x) : 반환값이 특정 수의 배수인지 확인하는 데코레이터
def is_multiple(x):
	def real_decorator(func):
    	def wrapper(a, b):
        	r = func(a, b)
            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))
# add의 반환값은 3의 배수입니다.
# 30
print(add(2, 5))
# add의 반환값은 3의 배수가 아닙니다.
# 7

* 가변 인수 함수 데코레이터

  • *args: 튜플 형태로 변수 저장
  • **kwargs : 딕셔너리 형태로 변수 저장
  • def add(a,b)는 매개변수의 개수가 고정된 함수였다. 매개변수(인수)가 고정되지 않은 함수를 처리할 때는, wrapper함수를 가변 인수 함수로 만들면 된다.
    • 데코레이터 실행
    def trace(func):
        def wrapper(*args, **kwargs):
            r = func(*args, **kwargs)
            print('{0}(args={1}, kwargs={2}) -> {3}'.format(func.__name__, args, kwargs, r))
            return r
        return wrapper
    @trace
    def get_max(*args): # 튜플 형태 인수를 사용
        return max(args) 
    @trace
    def get_min(**kwargs): # 딕셔너리 형태 인수를 사용
        return min(kwargs.values()) 
    • 결과확인
    print(get_max(10,20))
    #get_max(args=(10, 20), kwargs={}) -> 20
    #20
    print(get_min(x=1,y=2,z=3,q=5))
    #get_min(args=(), kwargs={'x': 1, 'y': 2, 'z': 3, 'q': 5}) -> 1
    #1 

* functools 모듈의 @wraps

  • @wraps에 func를 넣은 뒤 wrapper 함수 위에 지정
  • @functools.wraps는 원래 함수의 정보를 유지시켜주어 디버깅과 문서화할 때 유용하다.
  • 따라서 다른 개발자와의 협업에 유리한 면이 있기 때문에 decorator 함수를 만들때는 항상 사용해야 할 것 같다.

> @functools.wraps 사용 전

  • 여러개 데코레이터 지정
@is_multiple(3)
@is_multiple(7)
def add(a, b):
    return a + b 
add(10, 20)
  • 결과
add의 반환값은 7의 배수가 아닙니다.
wrapper의 반환값은 3의 배수입니다.

-> 가까운 decorator먼저 적용
-> 데코레이터를 여러 개 사용하면 데코레이터에서 반환된 wrapper함수가 다른 데코레이터로 들어가기 때문에, 함수의 func.__name__을 출력해보면 wrapper가 나옴

  • 따라서!!
    => 함수의 원래 이름을 출력, signature를 잘 보존하고 싶다면 from functools import wraps을 가져와 @wraps에 func을 넣은 뒤, wrapper함수 위에 지정해주어야 한다.

> @functools.wraps 사용 후

  • @wraps 사용
from functools import wraps
def is_multiple(x):
    def real_decorator(func):
        @wraps(func)    # @wraps에 func를 넣은 뒤 wrapper 함수 위에 지정
        def wrapper(a, b):
            r = func(a, b)
            if r % x == 0:
                print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, x))
            else:
                print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, x))
            return r
        return wrapper
    return real_decorator
@is_multiple(3)
@is_multiple(7)
def add(a, b):
    return a + b
add(10, 20)
  • 결과
add의 반환값은 7의 배수가 아닙니다.
add의 반환값은 3의 배수입니다.

->원래 함수의 정보를 유지시켜주기 때문에 func.__name__값이 원래대로 나온다.


Ref

profile
주니어 개발자 주니어발록 주니어예티 주니어레이스

0개의 댓글