Python Decorator 실전 가이드
즉 decorator의 핵심은 문법이 아니라 가시성입니다. 반복되는 정책을 경계에 잘 드러내면 유용하고, 예쁜 함수 시그니처 뒤에 너무 많은 동작을 숨기면 곧 해로워집니다.
decorator가 진짜 유용한 구간
decorator는 반복되는 policy를 여러 함수에 붙여야 할 때 가장 좋습니다.
- 인증과 권한 확인
- retry와 timeout
- tracing과 metrics
- caching
- plugin/routing 등록
이런 경우 핵심 가치는 문법 그 자체보다, 이 함수에는 어떤 정책이 붙는가를 코드 경계에서 바로 보여 준다는 점입니다.
decorator가 위험해지는 이유
decorator가 비싸지는 순간은 visible policy가 아니라 hidden control flow가 될 때입니다.
대표적인 문제는:
- 반환 타입을 조용히 바꾸는 경우
- 예외를 예상 못 하게 삼켜 버리는 경우
- 호출 인자를 몰래 변경하는 경우
- 가벼워 보이는 wrapper 안에 I/O를 숨기는 경우
- decorator를 여러 개 겹쳐 실행 순서가 읽히지 않는 경우
decorator는 추가하기 쉬운 만큼, 과용도 쉽습니다.
실무 규칙: decorator는 설명 가능해야 한다
좋은 decorator는 대체로 아래 특성을 가집니다.
- 무엇을 감싸는지 한 문장으로 설명 가능하다
- 이름이 실제 역할을 드러낸다
- 함수 이름, docstring 같은 메타데이터를 보존한다
- 실패 동작이 이해 가능하다
- 함수 계약을 놀랍게 바꾸지 않는다
이 규칙이 깨지는 순간, 리뷰와 디버깅 비용이 크게 올라갑니다.
functools.wraps는 선택이 아니다
production Python에서 아주 작지만 중요한 포인트가 functools.wraps 입니다.
이게 없으면:
- 로그가 읽기 어려워지고
- tracing 도구가 함수 정체성을 잃고
- 문서화와 introspection이 망가지고
- stack trace 가치가 떨어집니다
decorator 품질이 결국 우아함이 아니라 운영 가능성의 문제라는 점을 잘 보여 주는 사례입니다.
예시: 쓸 만한 retry decorator
from collections.abc import Callable
from functools import wraps
import time
def retry(times: int, delay_seconds: float) -> Callable:
def decorator(fn: Callable) -> Callable:
@wraps(fn)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(times):
try:
return fn(*args, **kwargs)
except Exception as exc:
last_error = exc
if attempt == times - 1:
raise
time.sleep(delay_seconds)
raise last_error
return wrapper
return decorator
이 예시가 괜찮은 이유는 짧아서가 아니라 아래가 모두 분명하기 때문입니다.
- 정책이 분명하다
- 계약이 단순하다
- 메타데이터가 보존된다
- 실패 방식이 설명 가능하다
좋은 decorator는 대개 이런 식으로 읽힙니다.
프레임워크 환경에서는 더 조심해야 한다
Python 프레임워크는 routing, dependency injection, task registration, caching에 decorator를 자주 씁니다. 자연스러운 흐름이지만, 그만큼 이해 기준도 더 엄격해져야 합니다.
특히 알아야 할 것은:
- import 시점에 실행되는가
- call 시점에 실행되는가
- 등록만 바꾸는가, 런타임 동작도 바꾸는가
- 여러 decorator가 어떤 순서로 합성되는가
이걸 모르면 프레임워크 코드는 금방 “좋지 않은 마법”처럼 느껴집니다.
자주 보는 안티패턴
- 복잡한 orchestration을 decorator 뒤에 숨기는 경우
- critical path에 wrapper를 너무 많이 겹치는 경우
- 반환 계약을 바꾸면서도 그걸 분명히 드러내지 않는 경우
@wraps를 빼먹는 경우- 명시적 wrapper/helper가 더 나은데도 decorator부터 쓰는 경우
decorator가 나쁜 이유는 간접적이어서가 아니라, 간접성의 크기가 가독성 이득보다 커지는 순간부터입니다.
리뷰 체크리스트
- 반복되는 policy를 정말 잘 드러내는가
- 명시적 helper나 wrapper가 더 이해하기 쉽지 않은가
@wraps로 메타데이터를 보존하는가- 에러와 retry 동작이 운영 관점에서도 충분히 보이는가
- 새 팀원이 최종 실행 순서를 설명할 수 있는가
마무리 판단
Python decorator는 cross-cutting concern을 적절한 경계에 드러낼 때 가장 강합니다. 반대로 동작을 마법처럼 보이게 만들면 곧 위험해집니다. 성숙한 코드베이스에서 좋은 decorator는 대개 사고가 났을 때도 설명이 쉬운 decorator 입니다.
Continue Reading
다음으로 읽기 좋은 글
Java Stream API 실전 가이드
Java Stream API를 어디서 쓰면 코드가 더 명확해지고, 어디서는 오히려 for-loop가 더 안전한지 실무 기준으로 정리합니다.
💬 LanguagePython asyncio 비동기 프로그래밍 실전 가이드
Python asyncio를 실서비스에서 쓸 때 어디서 이점이 나는지, timeout과 cancellation을 어떻게 설계해야 하는지, 어떤 실패 패턴을 경계해야 하는지 정리합니다.
📈 최신 동향JDK 25 최신 동향: LTS 채택을 어떻게 읽어야 하는가
JDK 25는 2025년 9월 16일 GA가 되었고 Java 25의 기준 구현입니다. 지금 중요한 것은 JEP 개수보다, 어떤 기능을 실전 채택 대상으로 보고 어떤 것은 관망해야 하는지입니다.
⚙️ BackendPython FastAPI로 REST API 빠르게 만들기
FastAPI 데모를 넘어서 라우팅, Pydantic 스키마, 의존성 주입, 인증 경계, 예외 처리, 운영 체크포인트까지 실무형 REST API 기준으로 정리합니다.
다음 탐색