-
[Python] 파이썬 데코레이터(decorator)를 제대로 이해해보자IT 2026. 1. 21. 14:40

파이썬을 어느 정도 사용하다 보면 @decorator 라는 문법을 마주하게 됩니다.
처음에는 단순히 "함수 위에 붙이는 무언가" 처럼 보이지만, 조금만 깊이 들어가면 데코레이터는 파이썬의 함수 객체 개념, 고차함수, 클로저가 모두 결합된 매우 종요한 개념이라는 것을 알게 됩니다.
이 글에서는 데코레이터는 문법 위주로 설명하기보다는, 왜 이런 개념이 필요했는지, 그리고 어떤 사고 흐름으로 이해해야 자연스러운지를 중심으로 풀어보려고 합니다.
데코레이터를 이해하기 전에 꼭 짚고 가야할 이야기
데코레이터를 이해하지 못하는 가장 큰 이유는, 사실 데로케이터 자체가 어렵기 때문이 아니라 그 바탕에 있는 개념들을 충분히 소화하지 못했기 때문인 경우가 많습니다.
파이썬에서 함수는 단순한 코드 묶음이 아니라, 값처럼 취급되는 객체 입니다. 함수는 변수에 할당될 수 있고, 다른 함수의 인자로 전달될 수 있으며, 심지어 함수의 반환 값이 될 수도 있습니다.
def hello(): print("hello") say = hello say()이 코드에서 hello와 say는 사실상 같은 대상을 가리킵니다. 이처럼 파이썬에서는 함수 이름이 곧 함수를 가리키는 참조(reference) 일 뿐입니다.
이 점을 이해하는 것이 데코레이터의 출발점입니다.
함수를 감싼다는 것은 무슨 의미일까?
데코레이터는 흔히 "함수를 감싼다"라고 표현합니다. 이 말이 추상적으로 들릴 수 있지만, 실제로는 아주 단순한 의미를 가집니다.
어떤 함수가 실행되기 전이나 후에 공통으로 수행하고 싶은 로직이 있다고 가정해봅시다.
예를 들어 로그를 남기거나, 실행 시간을 측정하거나, 권한을 검사하는 로직이 그렇습니다.
가장 직관적인 방법은 함수 안에 직접 코드를 추가하는 것입니다. 하지만 이렇게 되면 같은 코드가 여러 함수에 반복되고, 함수의 본래 역할도 흐려지게 됩니다.
그래서 우리는 이런 생각을 하게 합니다.
"함수의 본래 기능은 그대로 두고, 바깥에서 기능을 덧붙일 수 없을까?"
이 질문에 대한 파이썬의 대답이 바로 데코레이터입니다.
데코레이터의 본질은 고차함수이다
데코레이터는 겉보기에는 특별한 문법처럼 보이지만, 본질적으로 고차함수입니다. 즉, 함수를 인자로 받아서 새로운 함수를 반환하는 함수입니다.
아직 @ 문법을 쓰지 말고, 가장 원초적인 형태부터 살펴보겠습니다.
def my_decorator(func): def wrapper(): print("함수 실행 전") func() print("함수 실행 후") return wrapper이 함수는 다른 함수를 받아서, 그 함수를 감싸는 새로운 함수를 만들어 반환합니다. 여기서 wrapper 함수는 바깥 함수의 매개변수인 func를 기억하고 있는데, 이것은 바로 클로저입니다.
이제 이 데코레이터를 실제 함수에 적용해보겠습니다.
def hello(): print("hello") hello = my_decorator(hello) hello()출력 결과를 보면, 원래 hello 함수의 앞뒤로 새로운 동작이 추가된 것을 확인할 수 있습니다.
중요한 점은, hello라는 이름이 이제 새로운 함수(wrapper)를 가리키게 되었다는 사실입니다.
@ 문법은 단지 문법적 설탕일 뿐이다
위에서 작성한 코드는 분명히 잘 동작하지만, 매번 hello = my_decorator(hello) 같은 코드를 작성하는 것은 번거롭습니다.
그래서 파이썬은 이를 더 간결하게 표현할 수 있는 문법을 제공합니다.
@my_decorator def hello(): print("hello")이 코드는 내부적으로 다음과 완전히 동일하게 동작합니다.
def hello(): print("hello") hello = my_decorator(hello)즉 @decorator 문법은 새로운 개념이 아니라, 이미 배운 고차함수 구조를 보기 좋게 표현한 것에 불과합니다.
이 사실을 이해하면 데코레이터에 대한 부담이 크게 줄어듭니다.
인자를 받는 함수에도 데코레이터를 적용하려면
현실에서는 대부분의 함수가 인자를 받습니다. 그렇기 때문에 데코레이터 역시 이런 상황을 고려해야 합니다.
def my_decorator(func): def wrapper(*args, **kwargs): print("함수 실행 전") result = func(*args, **kwargs) print("함수 실행 후") return result return wrapper*args 와 **kwargs 를 사용하는 이유는, 데코레이터가 감싸는 함수의 형태를 일일이 신경 쓰지 않기 위해서입니다.
이렇게 하면 어떤 함수든 동일한 데코레이터를 적용할 수 있습니다.
데코레이터가 실제로 가장 많이 쓰이는 순간들
데코레이터는 단순한 예제를 넘어서, 실제 파이썬 코드 곳곳에 사용됩니다. 웹 프레임워크에서 로그인 여부를 확인하거나, 함수 실행 시간을 측정하거나, 캐시를 적용하는 경우가 대표적입니다.
import time def timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"실행 시간: {end - start:.4f}초") return result return wrapper @timer def slow_function(): time.sleep(1) slow_function()이 코드에서 핵심은, slow_function의 본래 역할은 전혀 바뀌지 않았다는 점입니다. 데코레이터는 오직 부가적인 관심사만을 책임집니다.
데코레이터를 이해하는 가장 중요한 관점
데코레이터를 잘 쓰기 위해 가장 중요한 것은 문법이 아니라 관점입니다. 데코레이터는 다음과 같은 질문에 대한 답이라고 볼 수 있습니다.
"이 함수가 하는 일은 그대로 두고, 공통 기능만 깔끔하게 분리할 수 없을까?"
이 질문에 자연스럽게 고개가 끄덕여진다면, 데코레이터는 더 이상 어려운 문법이 아니라 우아한 설계 도구로 보기이 시작할 것입니다.
데코레이터에 인자를 전달한다는 것은
앞에서 살펴본 데코레이터는 모두 @decorator 형태로 사용되었고, 데코레이터 자체는 인자를 받지 않았습니다.
하지만 실제 개발에서는 다음과 같은 요구가 자주 등장합니다.
" 같은 데코레이터인데, 상황에 따라 설정 값만 바꿔서 쓰고 싶다"
예를 들어 로그를 남기더라도 로그 레벨은 바꾸고 싶거나, 권한 체크 데코레이터에서 필요한 권한 이름을 전달하고 싶은 경우가 그렇습니다.
이때 필요한 것이 바로 데코레이터에 인자를 전달하는 구조입니다. 중요한 점은, 이 구조가 새로운 개념이 아니라 함수를 한 겹 더 감싼 형태라는 것입니다.
def repeat(count): def decorator(func): def wrapper(*args, **kwargs): for _ in range(count): func(*args, **kwargs) return wrapper return decorator이 코드를 처음 보면 복잡해 보일 수 있지만, 흐름을 차분히 따라가 보면 어렵지 않습니다.
repeat(count) 는 설정 값을 받는 함수이고, 그 안에서 실제 데코레이터를 반환하며 최종적으로 wrapper가 원래 함수를 감싸 실행합니다.
사용하는 쪽에서는 다음과 같이 자연스럽게 사용할 수 있습니다.
@repeat(3) def hello(): print("hello") hello()이 코드를 말로 풀어보면, "hello 함수를 세 번 실행하도록 감싼다" 라는 의미가 됩니다.
functools.wraps는 왜 필요할까?
데코레이터를 사용하다 보면, 예상치 못한 현상을 하나 마주하게 됩니다. 바로 데코레이터를 적용한 함수의 이름이나 설명이 바뀌어 버린다는 점입니다.
def my_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @my_decorator def hello(): """인사 함수""" print("hello") print(hello.__name__) print(hello.__doc__)출력 결과를 보면, 우리가 기대한 hello 가 아니라 wrapper라는 이름이 나오고, docstring도 사라진 것을 확인할 수 있습니다.
이는 데코레이터가 원래 함수를 새로운 함수로 교체했기 때문에 발생하는 자연스러운 결과입니다.
하지만 디버깅이나 문서화, 혹은 프레임워크 내부 동작에서는 이런 정보들이 매우 중요합니다. 이 문제를 해결하기 위해 파이썬은 functools.wraps 를 제공합니다.
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper@wrap(func) 한 줄이 추가되었을 뿐이지만, 이 한 줄은 원래 함수의 이름, 설명, 메타데이터를 그대로 wrapper 함수에 복사해주는 역할을 합니다.
즉, functools.wraps 는 단순한 장식이 아니라, 데코레이터를 안전하고 예측 가능하게 사용하기 위한 필수 도구라고 볼 수 있습니다.
마무리하며
데코레이터는 파이썬을 파이썬답게 만들어주는 중요한 기능입니다. 처음에는 낯설고 어렵게 느껴질 수 있지만, 그 안을 들여다보면 결국 우리가 이미 알고 있던 고차함수와 클로저의 자연스러운 확장이라는 사실을 알 수 있습니다.
문법을 외우기보다는, 왜 이런 구조가 필요했는지를 이해하려고 한다면 데코레이터는 훨씬 친근한 개념이 될 것입니다.
다음에는 클로저에 대해서 알아보도록 하겠습니다.
읽어주셔서 감사합니다.
'IT' 카테고리의 다른 글
[Python] 파이썬 클로저(Closure)를 이해한다는 것 (0) 2026.01.21 [Python] 고차함수(Higher-Order Function) 란? (0) 2026.01.21 [Python] Higher-Order Functions와 Generator로 Django FCM 코드 중복 제거 (0) 2026.01.21 블로그 7개월. 공백의 이유 (0) 2026.01.09 [Flutter] 크로스 플랫폼의 함정: Android 15 대응이 iOS UI를 망가뜨렸을 때 (1) 2025.06.23