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

Updated Feb 6, 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

실전 커스텀 데코레이터

성능 측정 데코레이터

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?

☕ Buy me a coffee

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

TODAY 473 | TOTAL 2,696