데코레이터: Python에서 가장 강력한 기술
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Python 데코레이터에 대한 자세한 설명
I. 데코레이터란 무엇인가요
Python에서 데코레이터는 본질적으로 Python 함수입니다. 데코레이터에는 원래 코드를 수정하지 않고도 다른 함수에 추가 기능을 추가할 수 있는 고유한 기능이 있습니다. 데코레이터의 반환 값 또한 함수 객체입니다. 간단히 말해서, 데코레이터는 다른 함수를 반환하도록 특별히 설계된 함수입니다.
데코레이터는 측면 지향 요구 사항이 존재하는 많은 시나리오에서 중요한 역할을 합니다. 예:
- 로그 삽입: 디버깅 및 시스템 모니터링에 유용한 함수의 실행 프로세스 및 관련 정보를 기록하는 데 도움이 됩니다.
- 성능 테스트: 함수의 실행 시간을 계산하여 성능을 평가할 수 있습니다.
- 트랜잭션 처리: 일련의 작업이 모두 성공하거나 모두 실패하도록 보장하여 데이터 일관성 및 무결성을 보장합니다.
- 캐싱: 계산 비용이 높은 함수의 경우 계산 결과를 캐시합니다. 다음에 동일한 입력이 발생하면 캐시된 값을 직접 반환하여 효율성을 향상시킵니다.
- 권한 확인: 사용자가 함수를 실행하기 전에 해당 권한을 가지고 있는지 확인하여 시스템 보안을 보장합니다.
데코레이터는 이러한 문제를 해결하기 위한 훌륭한 설계 솔루션을 제공합니다. 데코레이터를 사용하면 함수의 핵심 기능과 관련이 없지만 반복적으로 나타나는 많은 양의 코드를 추출하여 높은 수준의 코드 재사용을 달성할 수 있습니다.
요약하면 데코레이터의 핵심 기능은 기존 객체에 추가 기능을 추가하여 코드 구조를 더 명확하게 하고 기능을 더 풍부하고 유연하게 만드는 것입니다.
II. 데코레이터가 필요한 이유
(I) 간단한 예
먼저 간단한 함수를 고려해 보세요.
def foo(): print('i am foo')
이 함수는 단순히 i am foo
문자열을 출력합니다.
(II) 요구 사항 추가
이제 함수의 실행 로그를 기록해야 하는 새로운 요구 사항이 있습니다. 따라서 코드에 로그 관련 코드를 추가합니다.
def foo(): print('i am foo') print("foo is running")
이 시점에서 foo
함수는 원래 기능 외에도 로그를 출력하는 기능이 추가되었습니다.
(III) 더 많은 함수에 대한 요구 사항
이러한 로그 기록 요구 사항을 모두 추가해야 하는 함수가 100개 있다고 가정하고 앞으로 이러한 100개의 함수에 대해 실행 전에 로그를 출력하는 요구 사항을 추가해야 할 수도 있습니다. 함수 코드를 하나씩 수정하면 많은 양의 중복 코드가 생성되므로 분명히 좋은 해결책이 아닙니다.
중복 코드 작성을 줄이기 위해 로그 관련 작업을 특별히 처리하는 함수를 다시 정의할 수 있습니다. 로그 처리가 완료되면 실제 비즈니스 코드가 실행됩니다. 다음은 그 예입니다.
def use_logging(func): print("%s is running" % func.__name__) func() def bar(): print('i am bar') use_logging(bar)
실행 결과는 다음과 같습니다.
bar is running
i am bar
이 예에서 use_logging
함수는 데코레이터입니다. 이 함수는 함수 내에서 실제 비즈니스 메서드를 실행하는 func
를 래핑합니다. 공식적으로는 bar
함수가 use_logging
에 의해 데코레이션된 것처럼 보입니다. 함수가 들어가고 나갈 때의 로그 기록 작업을 측면이라고 하고 이 프로그래밍 방법을 측면 지향 프로그래밍이라고 합니다.
이 use_logging
함수를 통해 함수에 로깅 기능을 성공적으로 추가했습니다. 앞으로 로깅 함수를 추가해야 하는 함수가 얼마나 많든 또는 로그 형식을 수정해야 하는 경우 use_logging
함수를 수정하고 use_logging(데코레이션된 함수)
를 호출하여 원하는 효과를 얻기만 하면 됩니다. 예:
def use_logging(func): print("%s is running" % func.__name__) return func @use_logging def bar(): print('i am bar') bar()
III. 기본 데코레이터 소개
(I) 데코레이터 구문 슈가
Python은 @
기호를 데코레이터의 구문 슈가로 제공하여 데코레이션 함수를 더 편리하게 적용할 수 있습니다. 그러나 구문 슈가를 사용하기 위한 요구 사항이 있는데, 즉 데코레이션 함수가 함수 객체를 반환해야 한다는 것입니다. 따라서 일반적으로 데코레이션할 함수를 내부 함수로 래핑하고 이 내부 함수를 반환합니다.
다음 코드를 예로 들어 보겠습니다. 데코레이터 use_logging
은 먼저 이 함수를 실행한 다음 데코레이션된 함수 bar
를 반환하는 것과 같습니다. 따라서 bar()
가 호출되면 실제로 두 개의 함수를 실행하는 것과 같습니다. 즉, use_logging(bar)()
를 직접 호출하는 것과 같습니다.
def use_logging(func): def _deco(): print("%s is running" % func.__name__) func() return _deco @use_logging def bar(): print('i am bar') bar()
(II) 매개변수가 있는 함수 데코레이션
함수가 두 개의 매개변수를 받아 계산을 수행해야 하는 경우 내부 함수를 해당 매개변수 a
및 b
를 받도록 변경해야 합니다. 이 때 bar(1, 2)
를 호출하는 것은 use_logging(bar)(1, 2)
를 호출하는 것과 같습니다. 샘플 코드는 다음과 같습니다.
def use_logging(func): def _deco(a, b): print("%s is running" % func.__name__) func(a, b) return _deco @use_logging def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 2)
그러나 실제 적용에서 데코레이션하는 함수의 매개변수 수와 유형은 다를 수 있습니다. 매개변수 상황이 다를 때마다 데코레이터를 수정하는 것은 분명히 비과학적입니다. 이 매개변수 문제를 해결하기 위해 Python의 가변 길이 매개변수 *args
및 **kwargs
를 사용할 수 있습니다.
(III) 불확실한 수의 함수 매개변수
다음은 매개변수가 없는 데코레이터 버전이며, 이 형식은 매개변수가 없는 함수를 데코레이션하는 데 적합합니다. *args
및 **kwargs
를 사용하면 데코레이터가 이미 다양한 길이와 유형의 매개변수에 적응할 수 있습니다. 즉, 이 데코레이터 버전은 모든 유형의 매개변수가 없는 함수를 데코레이션할 수 있습니다. 샘플 코드는 다음과 같습니다.
def use_logging(func): def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(a, b): print('i am bar:%s' % (a + b)) @use_logging def foo(a, b, c): print('i am bar:%s' % (a + b + c)) bar(1, 2) foo(1, 2, 3)
(IV) 매개변수가 있는 데코레이터
경우에 따라 데코레이터가 매개변수를 가져오도록 해야 합니다. 이를 위해서는 데코레이터를 반환하는 고차 함수를 작성해야 하며, 이는 구현하기가 비교적 더 복잡합니다. 예:
#! /usr/bin/env python def use_logging(level): def _deco(func): def __deco(*args, **kwargs): if level == "warn": print "%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging(level="warn") def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3) # use_logging(level="warn")(bar)(1, 3)와 동일
(V) functools.wraps
데코레이터를 사용하면 코드를 크게 재사용할 수 있지만 원래 함수의 메타 정보가 손실된다는 단점이 있습니다. 예를 들어 함수의 docstring
, __name__
및 매개변수 목록과 같은 정보입니다. 먼저 다음 예를 살펴보겠습니다.
def use_logging(func): def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() # 출력 결과는 다음과 같습니다. # bar is running # i am bar # _deco
함수 이름이 원래 bar
대신 _deco
가 된 것을 알 수 있습니다. 리플렉션 기능을 사용할 때 이 상황은 문제를 일으킬 수 있습니다. 이 문제를 해결하기 위해 functools.wraps
를 도입할 수 있습니다. functools.wraps
를 사용하는 샘플 코드는 다음과 같습니다.
import functools def use_logging(func): @functools.wraps(func) def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() # 출력 결과는 다음과 같습니다. # bar is running # i am bar # bar
위의 결과에서 볼 수 있듯이 functools.wraps
를 사용한 후 예상되는 결과를 얻고 원래 함수의 이름을 성공적으로 유지합니다.
(VI) 매개변수가 있거나 없는 데코레이터에 대한 적응성 달성
import functools def use_logging(arg): if callable(arg): # 전달된 매개변수가 함수인지 확인합니다. 매개변수가 없는 데코레이터는 이 분기를 호출합니다. @functools.wraps(arg) def _deco(*args, **kwargs): print("%s is running" % arg.__name__) arg(*args, **kwargs) return _deco else: # 매개변수가 있는 데코레이터는 이 분기를 호출합니다. def _deco(func): @functools.wraps(func) def __deco(*args, **kwargs): if arg == "warn": print "warn%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging("warn") # @use_logging def bar(): print('i am bar') print(bar.__name__) bar()
IV. 클래스 데코레이터
클래스 데코레이터를 사용하면 매개변수가 있는 데코레이터의 효과를 얻을 수 있을 뿐만 아니라 구현 방법도 더 우아하고 간결합니다. 동시에 상속을 통해 유연하게 확장할 수 있습니다.
(I) 클래스 데코레이터
class loging(object): def __init__(self, level="warn"): self.level = level def __call__(self, func): @functools.wraps(func) def _deco(*args, **kwargs): if self.level == "warn": self.notify(func) return func(*args, **kwargs) return _deco def notify(self, func): # logit은 로깅만 하고 다른 작업은 수행하지 않습니다. print "%s is running" % func.__name__ @loging(level="warn") # __call__ 메서드를 실행합니다. def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3)
(II) 클래스 데코레이터 상속 및 확장
class email_loging(Loging): ''' 함수가 호출될 때 관리자에게 이메일을 보낼 수 있는 로깅 구현 버전 ''' def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_loging, self).__init__(*args, **kwargs) def notify(self, func): # self.email로 이메일을 보냅니다. print "%s is running" % func.__name__ print "sending email to %s" % self.email @email_loging(level="warn") def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3)
위의 코드에서 email_loging
클래스는 Loging
클래스에서 상속됩니다. 이 상속 관계를 통해 원래 클래스의 핵심 로직을 변경하지 않고도 함수가 호출될 때 관리자에게 이메일을 보내는 것과 같은 새로운 함수를 추가할 수 있습니다. 이는 코드 확장 및 재사용에서 클래스 데코레이터의 장점을 완전히 반영합니다.
Leapcell: Python 앱 호스팅을 위한 차세대 서버리스 플랫폼
마지막으로 Python 서비스를 배포하기 위한 최고의 플랫폼인 **Leapcell**을 추천합니다.
1. 다국어 지원
- JavaScript, Python, Go 또는 Rust로 개발하십시오.
2. 무제한 프로젝트를 무료로 배포하세요
- 사용량에 대해서만 지불하세요. 요청도 없고 요금도 없습니다.
3. 타의 추종을 불허하는 비용 효율성
- 유휴 요금 없이 사용량만큼 지불하세요.
- 예: $ 25는 평균 응답 시간 60ms에서 694만 개의 요청을 지원합니다.
4. 간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
5. 간편한 확장성 및 고성능
- 고도의 동시성을 쉽게 처리하도록 자동 확장됩니다.
- 운영 오버헤드가 전혀 없습니다. 구축에만 집중하십시오.
Leapcell Twitter: https://x.com/LeapcellHQ