Python 강좌 / 데코레이터와 컨텍스트 매니저 이해하기

데코레이터는 함수의 호출 동작을 감싸고 컨텍스트 매니저는 작업 전후의 자원 수명을 관리합니다. 반복되는 공통 처리를 분리할 때 유용하지만 원래 함수의 반환·예외 계약을 바꾸지 않는지 확인해야 합니다.

감싸는 동작과 수명 관리

@decorator는 정의된 함수를 데코레이터에 전달한 결과로 이름을 연결하는 문법입니다. 단순히 주석처럼 표시하는 것이 아닙니다. with는 진입·종료 처리를 호출하며 예외가 나도 필요한 정리가 수행되도록 구성할 수 있습니다.

관련 동작과 지원 조건은 Python: contextlib에서 확인할 수 있습니다.

functools.wraps는 감싼 함수의 이름·문서 같은 메타데이터 보존을 돕습니다. 원래 함수의 모든 보안·자료형 계약을 자동으로 보장하는 기능은 아닙니다.

예외 정리와 지연 실행은 예외 처리하기: try·except·else·finally·raise에서 사용하는 규칙과 연결됩니다.

전체 예제와 실행 결과

별도의 실습 폴더에서 아래 파일을 UTF-8로 저장합니다. 가상환경을 선택한 터미널에서 python wrappers.py 명령으로 실행합니다. macOS·Ubuntu에서 가상환경을 사용하지 않는 경우 명령 이름을 python3으로 바꿉니다.

wrappers.py 파일의 전체 내용입니다.

from functools import wraps
from contextlib import contextmanager

def announce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("start")
        return func(*args, **kwargs)
    return wrapper

@contextmanager
def temporary_items():
    items = []
    try:
        yield items
    finally:
        items.clear()
        print("cleared")

@announce
def total(amounts):
    return sum(amounts)

with temporary_items() as items:
    items.extend([3500, 8000])
    print(total(items))
print(total.__name__)
print(items)

예상 결과는 다음과 같습니다.

start
11500
cleared
total
[]

예외가 생기는 지점과 전달

contextmanager 함수는 한 번 yield하여 with 본문에 값을 제공하고, 종료 시 그 뒤의 정리 코드를 진행합니다. 정리를 finally에 두어 본문에서 예외가 나도 실행되게 했습니다. 예외를 잡고 그대로 삼키면 호출자는 실패를 모를 수 있으므로 의도한 경우가 아니면 다시 전달합니다.

데코레이터에서 원래 함수 반환값을 return하지 않으면 호출 결과가 None으로 바뀝니다. 매개변수 전달과 예외 전달도 감싸기 전후 동일한지 확인하세요.

@announce는 함수 정의 후 func = announce(func)를 적용하는 것과 같습니다. wraps는 원래 함수의 이름·문서 같은 메타정보를 보존하고 호출 규칙을 대신 검증하지는 않습니다. contextmanager는 진입 코드 → yield로 본문에 제어 전달 → 본문 정상 종료 또는 예외 → finally 정리 순서입니다. yield 주변에서 예외를 잡고 다시 던지지 않으면 예외를 처리한 것으로 간주될 수 있으므로 의도한 복구인지 확인합니다.

이 동작과 적용 조건의 공식 설명: Python: functools.wraps

적절한 사용 범위

재시도 데코레이터는 편리해 보여도 쓰기 요청을 반복해 거래를 중복 생성할 수 있습니다. 자원 정리용 with와 작업 재시도 정책을 구분합니다. 작은 코드에서 한 번만 쓰는 처리를 무조건 추상화할 필요도 없습니다.

  1. with 본문에서 의도적으로 ValueError를 발생시켜 cleared가 출력되는지 확인합니다.
  2. 정리 후 리스트가 빈 상태인지 확인합니다.
  3. wrapper의 return을 빼면 결과가 바뀌는지 관찰하고 원래대로 복구합니다.