본문 바로가기
공부/Python

Python - 데코레이터(decorator)를 알아보자

by hhhello 2024. 6. 17.

데코레이션 - 네이버 지식백과

파이썬에 ‘@’키워드가 뭔지 궁금했다. 알고보니 데코레이터라는 거라고 한다.

데코레이터란?

@classmethod, @staticmethod, @property과 같이 ‘@’키워드가 붙은 건 전부 파이썬의 데코레이터이다.

또, 데코레이터는 함수를 장식할 수 있다는 특징이 있다.

데코레이터는 fastapi로 서버를 만들때나 discord봇을 만들 때 사용하는 걸 봤다. getter, setter를 구현할때도 쓰인다. 앞으로 많이 사용할 것 같으니 그 원리를 정확하게 이해하고 가자!

이 데코레이터라는 것을 한번 만들어보자.

def log(func):
    def wrapper(*args, **kwargs):
        print(f'start {func.__name__} function')
        func(*args, **kwargs)  # 진짜 함수 실행
        print(f'end {func.__name__} function')
    return wrapper  # 장식할😊 함수를 반환

@log
def printHello():
    print("Hello")

printHello()

위 코드는 함수 위에 @log만 붙이면 아래와 같은 로그를 다 띄워준다.

start printHello function
Hello
end printHello function

 


 

wow데코레이터를 하나 더 만들어봤다. 데코레이터는 여러개 장착할 수 있다. 이때 실행 순서는 위에서 아래로 실행이 된다.

def wow(func):
    def wrapper(*args, **kwargs):
        print('wow')
        func(*args, **kwargs)

    return wrapper

@log
@wow
def printHello():
    print("Hello")

실행 결과는 아래와 같다. start어쩌구가 먼저 실행이 된 후 wow가 출력된 것을 볼 수 있다.

start wrapper function
wow
Hello
end wrapper function

 


 

함수 호출을 제한하는 데코레이터도 만들 수 있다.

def limit_calls_decorator(max_calls):
    def decorator(func):
        calls = 0

        def wrapper(*args, **kwargs):
            nonlocal calls
            if calls < max_calls:
                calls += 1
                return func(*args, **kwargs)
            else:
                raise Exception("함수 호출 횟수 초과")

        return wrapper

    return decorator

아래 실행 결과를 보면 3번째 실행할 때까지는 문제가 없었지만 그 이후는 예외가 발생한다.

예제 함수 실행
예제 함수 실행
예제 함수 실행
함수 호출 횟수 초과
함수 호출 횟수 초과
Traceback (most recent call last):
.. 어쩌구 저쩌구 에러~

 


 

클래스 안에서도 데코레이터를 사용할 수 있다.

class TestExample:
    def _decorator(func):
        def wrap(self, *args, **kargs):
            print("Start", func.__name__)
            func(self, *args, **kargs)
            print("End", func.__name__)

        return wrap

    @_decorator
    def test(self, a, b, c):
        print("Variables :", a, b, c)

t = TestExample()
t.test("1", 2, c="345")

 


 

클래스를 사용해서도 데코레이터를 만들 수 있다.

아래는 함수가 호출될 때마다 count변수를 증가시킨다. 함수의 호출 횟수를 알 수 있다는 말이다.

class emoticon:
		def __init__(self, emoticon):
				self.count = 0  # 호출 회수 저장 필드
				self.emoticon = emoticon
				
		def __call__(self, func):
				def inner(*args, **kwargs):
						self.count += 1
						inner.count = self.count  # 함수 객체에서 바로 사용 할 수 있도록
						print(self.emoticon, ' ', end='')
						return func(*args, **kwargs)
				
				return inner
		
@emoticon('-_-')
def say_hi(name):
		print(f'hi {name}!')

say_hi('Jason')
print(say_hi.count)

 

데코레이터의 장점과 활용

데코레이터를 사용하면 중복되는 코드를 줄여 코드 가독성을 높이고 유지보수성을 높일 수 있다.
관심사의 분리도 할 수 있다. 함수는 함수의 핵심 기능에만 집중하고 나머지 부가기능은 데코레이터에게 맞기는 것이다.
데코레이터로 만들 수 있는 것은 무궁무진하다! 함수의 실행 시간을 측정하는 타이머나 로깅, REST API구축 등등…

 

데코레이터 주의사항

데코레이터 사용 시 함수의 메타데이터가 변경될 수 있다. 또, 여러 데코레이터를 사용할 때 실행 순서 또한 주의해야 한다.