Python 데코레이터 완전 정복: @property부터 커스텀 데코레이터까지 실전 활용 가이드

Updated Feb 13, 2026

데코레이터란 무엇인가?

데코레이터(Decorator)는 함수나 클래스를 수정하지 않고 기능을 추가하거나 변경할 수 있는 파이썬의 강력한 문법입니다. @ 기호로 시작하는 이 패턴은 코드의 재사용성을 높이고, 중복을 제거하며, 가독성을 향상시킵니다.

데코레이터는 본질적으로 “함수를 받아서 함수를 반환하는 함수”입니다.

기본 구조를 살펴보겠습니다.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("함수 실행 전")
        result = func(*args, **kwargs)
        print("함수 실행 후")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"안녕하세요, {name}님!")

say_hello("김철수")
# 출력:
# 함수 실행 전
# 안녕하세요, 김철수님!
# 함수 실행 후

내장 데코레이터 마스터하기

파이썬은 실무에서 자주 사용하는 내장 데코레이터를 제공합니다.

@property: 속성처럼 사용하는 메서드

@property는 메서드를 속성처럼 접근할 수 있게 만들어 캡슐화를 구현합니다.

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):
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.radius)  # 5 (메서드지만 속성처럼 접근)
print(c.area)    # 78.53975
c.radius = 10    # setter 호출

@staticmethod와 @classmethod

두 데코레이터의 차이를 명확히 이해하는 것이 중요합니다.

데코레이터 첫 번째 인자 용도 인스턴스 접근 클래스 접근
@staticmethod 없음 유틸리티 함수 불가 불가
@classmethod cls 팩토리 메서드, 클래스 변수 접근 불가 가능
class DateParser:
    default_format = "%Y-%m-%d"

    @staticmethod
    def is_valid_date(date_string):
        # 단순 유틸리티 함수
        return len(date_string) == 10 and date_string[4] == '-'

    @classmethod
    def from_timestamp(cls, timestamp):
        # 클래스 생성자 역할 (팩토리 메서드)
        from datetime import datetime
        return cls(datetime.fromtimestamp(timestamp))

@functools.wraps: 메타데이터 보존

데코레이터를 만들 때 원본 함수의 메타데이터를 보존하려면 @wraps를 사용해야 합니다.

from functools import wraps

def retry(max_attempts=3):
    def decorator(func):
        @wraps(func)  # 원본 함수의 __name__, __doc__ 보존
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"재시도 {attempt + 1}/{max_attempts}")
        return wrapper
    return decorator
Enjoying this article? Get more like it delivered to your inbox. Subscribe to the newsletter

실전 커스텀 데코레이터

성능 측정 데코레이터

import time
from functools import wraps

def measure_time(func):
    @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

@measure_time
def slow_function():
    time.sleep(2)
    return "완료"

권한 검사 데코레이터

웹 애플리케이션에서 자주 사용하는 패턴입니다.

def require_auth(permission):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if not hasattr(user, 'permissions'):
                raise PermissionError("권한 정보가 없습니다")
            if permission not in user.permissions:
                raise PermissionError(f"{permission} 권한이 필요합니다")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_auth('admin')
def delete_user(user, target_id):
    print(f"사용자 {target_id} 삭제")

캐싱 데코레이터

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(100))  # 즉시 계산 (캐시 덕분)

클래스 데코레이터

함수뿐만 아니라 클래스 전체를 데코레이트할 수도 있습니다.

def singleton(cls):
    instances = {}
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class DatabaseConnection:
    def __init__(self):
        print("DB 연결 생성")

db1 = DatabaseConnection()  # "DB 연결 생성" 출력
db2 = DatabaseConnection()  # 출력 없음 (같은 인스턴스)
print(db1 is db2)  # True

데코레이터 체이닝

여러 데코레이터를 동시에 적용할 수 있습니다. 아래에서 위로 적용됩니다.

@retry(max_attempts=3)
@measure_time
@require_auth('user')
def api_call(user, endpoint):
    # 실제 실행 순서:
    # 1. require_auth 검사
    # 2. measure_time으로 시간 측정 시작
    # 3. retry로 최대 3번 재시도
    pass

데코레이터 활용 시 주의사항

  • @wraps 항상 사용: 디버깅과 문서화를 위해 필수
  • 과도한 중첩 피하기: 3개 이상 체이닝은 가독성 저하
  • 상태 관리 주의: 데코레이터 내부 변수는 모든 호출에서 공유됨
  • 인자가 있는 데코레이터: 3중 중첩 함수 구조 필요 (데코레이터 팩토리 → 데코레이터 → wrapper)

마무리

데코레이터는 파이썬의 핵심 기능으로, 코드의 재사용성, 가독성, 유지보수성을 극대화합니다. 핵심 포인트를 정리하면:

  • @property로 깔끔한 인터페이스 설계
  • @staticmethod/@classmethod로 명확한 책임 분리
  • @wraps로 메타데이터 보존 필수
  • 커스텀 데코레이터로 횡단 관심사(로깅, 캐싱, 권한) 분리
  • 클래스 데코레이터로 싱글톤 등 패턴 구현

실무에서 반복되는 패턴을 발견하면 데코레이터로 추상화하는 습관을 들이세요. 처음엔 어렵게 느껴지지만, 익숙해지면 파이썬 코드의 품질이 한 단계 도약할 것입니다.

Did you find this helpful?

Your support keeps this blog running and ad-free content coming.

☕ Buy me a coffee
TODAY 2,266 | TOTAL 113,267