레슨 9 / 11·3개 토픽
데코레이터와 컨텍스트 매니저
함수 데코레이터
데코레이터는 함수를 인자로 받아 새로운 함수를 반환하는 고차 함수입니다. @decorator 구문으로 기존 함수에 기능을 추가할 수 있습니다. functools.wraps를 사용하면 원래 함수의 메타데이터(이름, 독스트링 등)를 보존할 수 있습니다.
python
import functools
import time
# 기본 데코레이터
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} 실행 시간: {elapsed:.4f}초")
return result
return wrapper
@timer
def slow_function():
"""느린 작업을 시뮬레이션합니다."""
time.sleep(1)
return "완료"
result = slow_function() # slow_function 실행 시간: 1.00xx초
print(slow_function.__name__) # slow_function (wraps 덕분)
# 인자를 받는 데코레이터
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"시도 {attempt}/{max_attempts} 실패: {e}")
raise Exception(f"{max_attempts}회 시도 모두 실패")
return wrapper
return decorator
@retry(max_attempts=3)
def unstable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("연결 실패")
return "성공"클래스 데코레이터: @property, @staticmethod, @classmethod
python
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""반지름 (읽기 전용처럼 접근)"""
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("반지름은 양수여야 합니다")
self._radius = value
@property
def area(self):
import math
return math.pi * self._radius ** 2
@staticmethod
def is_valid_radius(value):
"""인스턴스 없이 호출 가능"""
return isinstance(value, (int, float)) and value > 0
@classmethod
def from_diameter(cls, diameter):
"""지름으로 Circle 인스턴스 생성"""
return cls(diameter / 2)
c = Circle(5)
print(c.radius) # 5 (property getter)
print(c.area) # 78.5398... (계산된 속성)
c.radius = 10 # property setter 호출
print(Circle.is_valid_radius(-3)) # False (staticmethod)
c2 = Circle.from_diameter(20) # classmethod로 생성
print(c2.radius) # 10.0컨텍스트 매니저: with 문
컨텍스트 매니저는 리소스의 획득과 해제를 자동으로 관리합니다. with 문을 사용하면 예외가 발생해도 리소스가 안전하게 해제됩니다. __enter__와 __exit__ 메서드를 구현하거나, contextlib.contextmanager 데코레이터를 사용할 수 있습니다.
python
# 기본 with 문 (파일)
with open("data.txt", "w") as f:
f.write("Hello, World!") # 블록 종료 시 자동 close
# 클래스 기반 컨텍스트 매니저
class DatabaseConnection:
def __init__(self, host):
self.host = host
self.connected = False
def __enter__(self):
print(f"{self.host}에 연결")
self.connected = True
return self # as 변수에 바인딩
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"{self.host} 연결 해제")
self.connected = False
return False # True면 예외 억제
with DatabaseConnection("localhost") as db:
print(f"연결 상태: {db.connected}") # True
# 블록 종료 → __exit__ 자동 호출
# contextlib 데코레이터 방식
from contextlib import contextmanager
@contextmanager
def temp_directory():
import tempfile, shutil, os
dirpath = tempfile.mkdtemp()
print(f"임시 디렉토리 생성: {dirpath}")
try:
yield dirpath # yield 값이 as 변수에 바인딩
finally:
shutil.rmtree(dirpath)
print("임시 디렉토리 삭제 완료")
with temp_directory() as tmpdir:
filepath = tmpdir + "/test.txt"
with open(filepath, "w") as f:
f.write("임시 파일")- •
@decorator— 함수를 감싸서 기능을 추가하는 구문 - •
functools.wraps— 데코레이터 적용 시 원래 함수의 메타데이터 보존 - •
@property— 메서드를 속성처럼 접근 가능하게 만듦 - •
@staticmethod— 인스턴스/클래스에 의존하지 않는 유틸리티 메서드 - •
@classmethod— 클래스 자체를 첫 인자로 받아 팩토리 메서드 등에 활용 - •
__enter__/__exit__— 클래스 기반 컨텍스트 매니저의 핵심 메서드 - •
contextlib.contextmanager— 제너레이터로 간편하게 컨텍스트 매니저 생성
💡
데코레이터를 중첩하면 아래에서 위로 적용됩니다. @a @b def f():는 f = a(b(f))와 같습니다. 실무에서는 로깅, 캐싱, 인증, 재시도 등에 데코레이터를 널리 사용합니다.