데코레이터의 기본 개념
데코레이터(Decorator)는 함수 또는 클래스를 감싸서 추가적인 기능을 부여하는 기능이다. 이를 통해 코드를 수정하지 않고도 부가적인 동작(로깅, 인증, 캐싱 등)을 쉽게 추가할 수 있다.
데코레이터가 필요한 이유
- 코드 재사용: 여러 함수에 동일한 기능을 추가할 때 코드 중복을 피할 수 있다.
- 관심사 분리: 핵심 로직과 부가 기능(로깅, 타이밍, 검증 등)을 분리할 수 있다.
- 가독성 향상: 함수의 주요 로직을 깔끔하게 유지하면서 부가 기능을 추가할 수 있다.
데코레이터의 원리
Python의 함수는 함수 자체를 변수에 할당하거나, 다른 함수의 인자로 전달할 수 있다. 데코레이터는 이를 활용하여 함수를 인자로 받아 새로운 동작을 추가하는 방식으로 동작한다.
def greeting():
return "Hello, World!"
# 함수를 변수에 할당
say_hello = greeting
# 변수를 통해 함수 호출
print(say_hello()) # 출력: Hello, World!
데코레이터의 기본 형태
데코레이터는 이러한 개념들을 활용하여 다음과 같이 구현된다.
def my_decorator(func):
def wrapper():
print("코드 실행 전")
func()
print("코드 실행 후")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 이제 함수 호출
say_hello()
출력 결과:
코드 실행 전
Hello!
코드 실행 후
@my_decorator 구문은 아래 코드와 동일한 의미이다.
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)
인자를 처리하는 데코레이터
함수 인자 전달하기
함수가 인자를 받는 경우, 데코레이터도 이를 처리할 수 있어야 한다.
def my_decorator(func):
def wrapper(*args, **kwargs):
print("코드 실행 전")
result = func(*args, **kwargs)
print("코드 실행 후")
return result # return 값이 필요한 경우 사용한다.
return wrapper
@my_decorator
def greet(name):
print(f"Hello, {name}!")
greet("John Doe")
출력 결과:
코드 실행 전
Hello, John Doe!
코드 실행 후
위 구문은 아래 코드와 동일하다.
def say_hello(name):
print(f"Hello, {name}!")
say_hello = my_decorator(say_hello)
say_hello("John Doe")
데코레이터는 함수를 인자로 받아야 하는 데, 아래와 같이 사용하면 my_decorator(None)이 되어, 에러가 발생하게 된다.
def say_hello(name):
print(f"Hello, {name}!")
say_hello = my_decorator(say_hello("John Doe"))
say_hello()
데코레이터에 인자 전달하기
데코레이터 자체에 인자를 전달하려면 추가적인 중첩 함수가 필요하다.
def add_prefix(prefix): # 외부 함수: 데코레이터에 전달할 인자 받음
def decorator(func): # 실제 데코레이터: 함수를 인자로 받음
def wrapper(name): # 내부 래퍼: 원본 함수 대체 (내부 래퍼가 필요없는 경우 생략 가능)
return f"{prefix} {func(name)}"
return wrapper
return decorator
@add_prefix("Mr.")
def get_name(name):
return name
print(get_name("John Doe")) # 출력: Mr. John Doe
이 경우 @add_prefix("Mr.")는 다음 코드와 동일하다.
def get_name(name):
return name
who_am_i = add_prefix("Mr.")(get_name)("John Doe")
클래스 데코레이터나 직접 함수를 반환하는 패턴에서는 래퍼없이 데코레이터를 선언할 수 있다.
routes = {}
def route(path):
def decorator(func):
# 경로와 함수를 매핑
routes[path] = func
return func # 원본 함수 반환
return decorator
@route('/home')
def home():
return "홈페이지"
# 라우트 확인
print(routes)
다중 데코레이터 적용
하나의 함수에 여러 데코레이터를 적용할 수 있다.
def uppercase_decorator(func):
def wrapper():
result = func()
return result.upper()
return wrapper
def exclamation_decorator(func):
def wrapper():
result = func()
return result + "!!!"
return wrapper
@uppercase_decorator
@exclamation_decorator
def greet():
return "hello world"
print(greet()) # 출력: HELLO WORLD!!!
여기서 데코레이터는 아래에서 위로 적용된다. 이 예제에서는 @exclamation_decorator이 먼저 적용되고 그 다음 @uppercase_decorator가 적용된다.
functools 모듈의 유용한 데코레이터
@functools.wraps - 메타데이터 보존
데코레이터를 사용할 때 한 가지 문제점은 원래 함수의 메타데이터(이름, 독스트링, 인자 목록 등)가 래퍼 함수에 의해 가려진다는 것이다. 이렇게 되면 디버깅이나 문서화에 문제가 생길 수 있다.
def my_decorator(func):
def wrapper(*args, **kwargs):
"""Wrapper function"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def hello():
"""원본 함수의 독스트링"""
pass
print(hello.__name__) # 출력: wrapper
print(hello.__doc__) # 출력: Wrapper function
functools.wraps 데코레이터를 사용하면 이 문제를 해결할 수 있다. 그러니 왠만하면 @wraps를 사용하는 게 좋다.
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper function"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def hello():
"""원본 함수의 독스트링"""
pass
print(hello.__name__) # 출력: hello
print(hello.__doc__) # 출력: 원본 함수의 독스트링
@functools.lru_cache - 메모이제이션과 성능 최적화
lru_cache 데코레이터는 함수 호출 결과를 캐시하여, 동일한 인자로 함수가 다시 호출될 때 계산을 반복하지 않고 캐시된 결과를 반환한다. LRU(Least Recently Used) 캐시 방식으로 가장 오랫동안 사용되지 않은 항목을 우선 삭제하며, 이를 통해 중복 계산을 방지하고 속도를 향상시킨다. 재귀 함수 등에 활용할 수 있다.
from functools import lru_cache
import time
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 측정 시작
start = time.time()
print(fibonacci(100))
print(f"시간 소요: {time.time() - start:.10f}초")
# 두 번째 호출은 캐시된 결과를 사용하므로 매우 빠름
start = time.time()
print(fibonacci(100))
print(f"시간 소요: {time.time() - start:.10f}초")
maxsize 매개변수는 캐시할 최대 호출 수를 지정한다. None은 크기 제한이 없음을 의미한다.
클래스 데코레이터
클래스 데코레이터를 사용해 클래스에 속성/메서드를 추가하거나,메서드의 동작 수정할 수 있다.
def add_features(cls):
# 속성 추가
cls.version = "1.0"
# 메서드 추가
def get_info(self):
return f"{self.__class__.__name__} 객체, 버전: {self.__class__.version}"
cls.get_info = get_info
# 기존 add() 메서드의 반환값 수정
original_add = cls.add # 기존 메서드 저장
def new_add(self, a, b):
result = original_add(self, a, b) # 원래 add() 실행
return f"{result} 입니다!" # 반환값 변경
cls.add = new_add # 수정된 메서드 적용
return cls
@add_features
class Calculator:
def __init__(self, name):
self.name = name
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * b
# 실행 테스트
calc = Calculator("calc")
print(calc.version) # 1.0
print(calc.get_info()) # Calculator 객체, 버전: 1.0
print(calc.add(5, 3)) # 출력: 8입니다!
예제 : 실행 시간 측정 데코레이터
import time
from functools import wraps
def timer_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 실행 시간: {end_time - start_time:.4f}초")
return result
return wrapper
@timer_decorator
def sleep_function():
time.sleep(1)
return "Function completed"
print(sleep_function())
'python' 카테고리의 다른 글
파이썬 기초 - 메서드 유형과 매직 메서드 (0) | 2025.03.22 |
---|---|
파이썬 기초 - 클래스 속성 관리와 접근 제어 (0) | 2025.03.20 |
파이썬 기초 - 예외처리 (0) | 2025.03.19 |
파이썬 기초 - Context Manager (0) | 2025.03.19 |
파이썬 기초 - Mutable과 Immutable 객체 (0) | 2025.03.19 |
댓글