
decorator는 파이썬에서 함수를 감싸서 기능을 추가하는 문법입니다. `@login_required`, `@app.get`, `@classmethod`처럼 이미 많이 보지만, 처음에는 함수가 언제 실행되고 무엇이 바뀌는지 헷갈립니다.
핵심은 데코레이터를 특별한 마법이 아니라 함수를 받아 함수를 돌려주는 구조로 보는 것입니다. 이 글은 Python 공식 문서의 function definition과 functools.wraps 설명을 기준으로 decorator 흐름을 단계적으로 정리합니다.

decorator를 이해하기 위한 첫 단계
Python에서는 함수도 객체입니다. 함수 이름은 함수 객체를 가리키는 변수처럼 동작할 수 있고, 함수를 다른 함수의 인자로 넘길 수도 있습니다.
def hello():
return "hello"
fn = hello
print(fn())이 감각이 있어야 데코레이터를 이해하기 쉽습니다. 데코레이터는 기존 함수를 받아서, 다른 함수를 돌려주는 구조입니다.
가장 작은 데코레이터 구조
아래 코드는 함수를 감싸 실행 전후에 로그를 찍는 데코레이터입니다. `wrapper` 안에서 원래 함수를 호출하고, 그 앞뒤에 공통 동작을 추가합니다.
def trace(func):
def wrapper():
print("시작")
result = func()
print("끝")
return result
return wrapper
@trace
def greet():
print("안녕하세요")
greet()`@trace`는 `greet = trace(greet)`와 거의 같은 의미로 볼 수 있습니다. 원래 greet 함수가 trace를 통과하면서 wrapper 함수로 바뀌는 것입니다.
인자가 있는 함수를 감싸기
실제 함수는 인자를 받는 경우가 많습니다. 그래서 wrapper에는 보통 `*args`, `**kwargs`를 사용해 원래 함수의 인자를 그대로 넘깁니다.
def trace(func):
def wrapper(*args, **kwargs):
print(f"{func.__name__} 호출")
return func(*args, **kwargs)
return wrapper
@trace
def add(a, b):
return a + b
print(add(1, 2))이 구조는 데코레이터가 원래 함수의 인자 형태를 몰라도 감쌀 수 있게 해 줍니다. 다만 너무 넓게 열어두는 만큼 디버깅과 타입 힌트에는 주의가 필요합니다.
functools.wraps가 필요한 이유
wrapper를 반환하면 겉으로 보이는 함수 이름과 문서 정보가 wrapper 기준으로 바뀔 수 있습니다. 로그, 문서화, 프레임워크 introspection에서 문제가 될 수 있습니다.
from functools import wraps
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{func.__name__} 호출")
return func(*args, **kwargs)
return wrapper그래서 실무 데코레이터에는 `@wraps(func)`를 붙이는 습관이 좋습니다. 원래 함수의 이름, 문서 문자열, 애노테이션 등을 보존하는 데 도움이 됩니다.
인자를 받는 데코레이터
데코레이터 자체가 옵션을 받아야 할 때는 함수가 한 겹 더 필요합니다. 바깥 함수가 설정값을 받고, 안쪽 함수가 실제 func를 받습니다.
from functools import wraps
def retry(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for _ in range(times):
try:
return func(*args, **kwargs)
except Exception as exc:
last_error = exc
raise last_error
return wrapper
return decorator
@retry(times=3)
def unstable_call():
...처음에는 구조가 복잡해 보이지만, 순서대로 보면 `retry(3)`이 decorator를 만들고, 그 decorator가 함수를 감싸는 흐름입니다.
FastAPI와 Flask에서 보이는 데코레이터
웹 프레임워크에서 데코레이터는 라우팅, 인증, 의존성 연결 같은 메타 정보를 함수에 붙이는 데 자주 사용됩니다. 예를 들어 `@app.get(‘/users’)`는 특정 경로와 함수를 연결하는 선언처럼 보입니다.
이때 데코레이터가 항상 함수를 단순 wrapper로 감싸는 것은 아닙니다. 프레임워크에 따라 registry에 함수를 등록하고 원래 함수를 반환할 수도 있습니다. 그래서 데코레이터는 ‘함수에 공통 기능이나 의미를 덧붙이는 구조’로 넓게 이해하는 편이 좋습니다.
언제 쓰면 좋을까
- 여러 함수에 반복되는 로깅, 측정, 권한 확인을 분리할 때
- 함수 본문을 핵심 로직에 집중시키고 싶을 때
- 프레임워크가 요구하는 등록 문법을 사용할 때
- 테스트에서 공통 준비/정리 흐름을 재사용할 때
언제 조심해야 할까
데코레이터가 많아지면 실제 실행 흐름이 함수 본문 밖으로 숨습니다. 특히 여러 데코레이터를 겹쳐 쓰면 순서에 따라 결과가 달라질 수 있습니다.
- 데코레이터가 함수의 반환값을 바꾸는지 확인한다
- 예외를 삼키거나 다른 예외로 바꾸는지 확인한다
- `functools.wraps`로 메타데이터를 보존한다
- 공개 API에서는 데코레이터가 붙은 뒤의 타입과 동작을 문서화한다
- 단순 반복 제거를 위해 과한 추상화를 만들지 않는다
정리
파이썬 데코레이터는 특별한 문법처럼 보이지만, 중심에는 함수 객체와 wrapper가 있습니다. 함수를 입력으로 받고, 필요한 기능을 덧붙인 함수를 반환한다고 보면 흐름이 정리됩니다.
Python 웹 프레임워크에서 데코레이터 감각이 어떻게 이어지는지는 FastAPI Depends 사용 기준, FastAPI JWT 인증은 어디서 나눠야 할까, SQLAlchemy MissingGreenlet은 FastAPI에서 왜 자주 보일까와 함께 보면 좋습니다. 외부 기준은 Python Reference – Function definitions, Python functools – wraps, Python Tutorial – Defining Functions를 확인했습니다.