KR_Decorator - somaz94/python-study GitHub Wiki

Python λ°μ½”λ ˆμ΄ν„° κ°œλ… 정리


1️⃣ λ°μ½”λ ˆμ΄ν„° 기초

λ°μ½”λ ˆμ΄ν„°λŠ” ν•¨μˆ˜λ‚˜ 클래슀의 λ™μž‘μ„ μˆ˜μ •ν•˜κ±°λ‚˜ ν™•μž₯ν•˜λŠ” 방법이닀.

# κΈ°λ³Έ λ°μ½”λ ˆμ΄ν„°
def my_decorator(func):
    def wrapper():
        print("ν•¨μˆ˜ μ‹€ν–‰ μ „")
        func()
        print("ν•¨μˆ˜ μ‹€ν–‰ ν›„")
    return wrapper

# λ°μ½”λ ˆμ΄ν„° μ‚¬μš©
@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# 좜λ ₯:
# ν•¨μˆ˜ μ‹€ν–‰ μ „
# Hello!
# ν•¨μˆ˜ μ‹€ν–‰ ν›„

# λ°μ½”λ ˆμ΄ν„° μž‘λ™ 원리 μ΄ν•΄ν•˜κΈ°
# @my_decoratorλŠ” λ‹€μŒκ³Ό λ™μΌν•˜λ‹€:
# say_hello = my_decorator(say_hello)

βœ… νŠΉμ§•:

  • ν•¨μˆ˜ μˆ˜μ •
  • μ½”λ“œ μž¬μ‚¬μš©
  • 가독성 ν–₯상
  • ν•¨μˆ˜ ν™•μž₯
  • 원본 ν•¨μˆ˜ 보쑴


2️⃣ λ§€κ°œλ³€μˆ˜κ°€ μžˆλŠ” λ°μ½”λ ˆμ΄ν„°

ν•¨μˆ˜μ— μΈμžκ°€ μžˆμ„ λ•Œ λ°μ½”λ ˆμ΄ν„°λ₯Ό μ μš©ν•˜λŠ” 방법이닀.

def with_params(func):
    def wrapper(*args, **kwargs):
        print(f"λ§€κ°œλ³€μˆ˜: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@with_params
def add(a, b):
    return a + b

print(add(3, 4))  # λ§€κ°œλ³€μˆ˜: (3, 4), {}
                  # 7

# λ©”μ„œλ“œμ— 적용
class Calculator:
    @with_params
    def multiply(self, a, b):
        return a * b

calc = Calculator()
print(calc.multiply(5, 6))  # λ§€κ°œλ³€μˆ˜: (Calculator 객체, 5, 6), {}
                           # 30

βœ… νŠΉμ§•:

  • μœ μ—°ν•œ λ§€κ°œλ³€μˆ˜ 처리
  • ν•¨μˆ˜ λ°˜ν™˜κ°’ 처리
  • 디버깅 용이
  • λͺ¨λ“  νƒ€μž…μ˜ 인자 지원
  • 클래슀 λ©”μ„œλ“œμ—λ„ 적용 κ°€λŠ₯


3️⃣ λ°μ½”λ ˆμ΄ν„°μ— 인자 전달

λ°μ½”λ ˆμ΄ν„° μžμ²΄μ— λ§€κ°œλ³€μˆ˜λ₯Ό μ „λ‹¬ν•˜μ—¬ λ™μž‘μ„ μ»€μŠ€ν„°λ§ˆμ΄μ§•ν•  수 μžˆλ‹€.

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                results.append(func(*args, **kwargs))
            return results
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    import random
    greetings = ["Hello", "Hi", "Hey", "Howdy", "Greetings"]
    return f"{random.choice(greetings)} {name}"

print(greet("Alice"))  # 3개의 인사말 λͺ©λ‘ 좜λ ₯

# λ°μ½”λ ˆμ΄ν„° 쀑첩 λ§€κ°œλ³€μˆ˜
def log_level(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[{level}] ν•¨μˆ˜ '{func.__name__}' μ‹€ν–‰")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log_level(level="INFO")
def process_data(data):
    return data * 2

print(process_data(10))  # [INFO] ν•¨μˆ˜ 'process_data' μ‹€ν–‰
                         # 20

βœ… νŠΉμ§•:

  • λ°μ½”λ ˆμ΄ν„° μ„€μ •
  • 반볡 μ‹€ν–‰
  • μ»€μŠ€ν„°λ§ˆμ΄μ§•
  • μ„€μ • λ§€κ°œλ³€μˆ˜ 지원
  • 쀑첩 λ°μ½”λ ˆμ΄ν„° ꡬ쑰
  • λŸ°νƒ€μž„ λ§€κ°œλ³€μˆ˜ μ§€μ •


4️⃣ 클래슀 λ°μ½”λ ˆμ΄ν„°

클래슀λ₯Ό μ΄μš©ν•œ λ°μ½”λ ˆμ΄ν„° κ΅¬ν˜„ 방법이닀.

class Timer:
    def __init__(self, func):
        self.func = func
        self.count = 0
        
    def __call__(self, *args, **kwargs):
        import time
        start = time.time()
        result = self.func(*args, **kwargs)
        end = time.time()
        self.count += 1
        print(f"ν•¨μˆ˜ '{self.func.__name__}'의 {self.count}번째 호좜, μ‹€ν–‰ μ‹œκ°„: {end - start:.4f}초")
        return result

@Timer
def slow_function():
    import time
    time.sleep(1)

slow_function()  # ν•¨μˆ˜ 'slow_function'의 1번째 호좜, μ‹€ν–‰ μ‹œκ°„: 1.0010초
slow_function()  # ν•¨μˆ˜ 'slow_function'의 2번째 호좜, μ‹€ν–‰ μ‹œκ°„: 1.0008초

# 클래슀 μžμ²΄μ— λ°μ½”λ ˆμ΄ν„° 적용
def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class Database:
    def __init__(self, url):
        print(f"DB에 μ—°κ²° 쀑: {url}")
        self.url = url

db1 = Database("localhost:5432")  # DB에 μ—°κ²° 쀑: localhost:5432
db2 = Database("localhost:5432")  # 좜λ ₯ μ—†μŒ (같은 μΈμŠ€ν„΄μŠ€ λ°˜ν™˜)
print(db1 is db2)  # True

βœ… νŠΉμ§•:

  • 객체지ν–₯적 μ ‘κ·Ό
  • μƒνƒœ μœ μ§€
  • λ©”μ„œλ“œ μž¬μ‚¬μš©
  • 호좜 횟수 좔적
  • μΈμŠ€ν„΄μŠ€ 생성 μ œμ–΄
  • 클래슀 λ³€ν˜•


5️⃣ μ—¬λŸ¬ λ°μ½”λ ˆμ΄ν„° μ‚¬μš©

μ—¬λŸ¬ λ°μ½”λ ˆμ΄ν„°λ₯Ό ν•¨κ»˜ μ‚¬μš©ν•˜μ—¬ κΈ°λŠ₯을 μ‘°ν•©ν•  수 μžˆλ‹€.

def bold(func):
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic(func):
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

@bold
@italic
def hello(name):
    return f"Hello, {name}!"

print(hello("World"))  # <b><i>Hello, World!</i></b>

# λ°μ½”λ ˆμ΄ν„° 적용 μˆœμ„œ μ΄ν•΄ν•˜κΈ°
# μ•ˆμͺ½μ—μ„œ λ°”κΉ₯μͺ½ μˆœμ„œλ‘œ μ‹€ν–‰λœλ‹€
@bold       # 3. bold 적용
@italic     # 2. italic 적용
def goodbye(name):
    return f"Goodbye, {name}!"  # 1. μ›λž˜ ν•¨μˆ˜ μ‹€ν–‰

print(goodbye("Python"))  # <b><i>Goodbye, Python!</i></b>

βœ… νŠΉμ§•:

  • λ°μ½”λ ˆμ΄ν„° 체이닝
  • μˆœμ„œ μ€‘μš”μ„±
  • ν…μŠ€νŠΈ λ³€ν™˜ μ˜ˆμ‹œ
  • 쀑첩 μ‹€ν–‰ 흐름
  • 닀쀑 κΈ°λŠ₯ ν™•μž₯
  • μ½”λ“œ λͺ¨λ“ˆν™”


6️⃣ μ‹€μš©μ μΈ λ°μ½”λ ˆμ΄ν„° 예제

μ‹€μ œ ν”„λ‘œμ νŠΈμ—μ„œ μœ μš©ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλŠ” λ°μ½”λ ˆμ΄ν„° μ˜ˆμ œμ΄λ‹€.

import functools
import time
import logging

# ν•¨μˆ˜ 메타데이터 보쑴
def log_execution(func):
    @functools.wraps(func)  # 원본 ν•¨μˆ˜μ˜ 메타데이터 μœ μ§€
    def wrapper(*args, **kwargs):
        logging.info(f"ν•¨μˆ˜ {func.__name__} μ‹€ν–‰ μ‹œμž‘")
        result = func(*args, **kwargs)
        logging.info(f"ν•¨μˆ˜ {func.__name__} μ‹€ν–‰ μ™„λ£Œ")
        return result
    return wrapper

# μž¬μ‹œλ„ λ°μ½”λ ˆμ΄ν„°
def retry(max_attempts=3, delay=1):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    logging.warning(f"μ‹œλ„ {attempt}/{max_attempts} μ‹€νŒ¨: {e}")
                    time.sleep(delay)
        return wrapper
    return decorator

# 캐싱 λ°μ½”λ ˆμ΄ν„°
def memoize(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # ν‚€μ›Œλ“œ μΈμžλŠ” μ •λ ¬ν•˜μ—¬ μΌκ΄€λœ ν•΄μ‹œ 생성
        key = str(args) + str(sorted(kwargs.items()))
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]
    return wrapper

# νƒ€μž… 검사 λ°μ½”λ ˆμ΄ν„°
def validate_types(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        sig = func.__annotations__
        # μœ„μΉ˜ 인자 검사
        for i, (param_name, param_type) in enumerate(
            [(k, v) for k, v in sig.items() if k != 'return'][:len(args)]
        ):
            if not isinstance(args[i], param_type):
                raise TypeError(f"인자 '{param_name}'λŠ” {param_type}이어야 ν•˜μ§€λ§Œ {type(args[i])}μž…λ‹ˆλ‹€")
        
        # ν‚€μ›Œλ“œ 인자 검사
        for param_name, arg in kwargs.items():
            if param_name in sig and not isinstance(arg, sig[param_name]):
                raise TypeError(f"인자 '{param_name}'λŠ” {sig[param_name]}이어야 ν•˜μ§€λ§Œ {type(arg)}μž…λ‹ˆλ‹€")
        
        result = func(*args, **kwargs)
        # λ°˜ν™˜κ°’ 검사
        if 'return' in sig and not isinstance(result, sig['return']):
            raise TypeError(f"λ°˜ν™˜κ°’μ€ {sig['return']}이어야 ν•˜μ§€λ§Œ {type(result)}μž…λ‹ˆλ‹€")
        
        return result
    return wrapper

# λ°μ½”λ ˆμ΄ν„° μ‚¬μš© μ˜ˆμ‹œ
@log_execution
@retry(max_attempts=3)
@memoize
@validate_types
def fetch_data(url: str, timeout: int = 10) -> dict:
    """μ™ΈλΆ€ APIμ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” ν•¨μˆ˜"""
    import random
    if random.random() < 0.3:  # 30% ν™•λ₯ λ‘œ μ‹€νŒ¨
        raise ConnectionError("μ—°κ²° 였λ₯˜ λ°œμƒ")
    return {"data": f"URL {url}μ—μ„œ κ°€μ Έμ˜¨ 데이터", "timestamp": time.time()}

# λ‘œκΉ… μ„€μ •
logging.basicConfig(level=logging.INFO)

try:
    result = fetch_data("https://api.example.com", timeout=5)
    print(result)
    
    # 두 번째 호좜 - μΊμ‹œμ—μ„œ κ°€μ Έμ˜΄
    result2 = fetch_data("https://api.example.com", timeout=5)
    print("μΊμ‹œ 히트:", result is result2)
    
    # 잘λͺ»λœ νƒ€μž… 전달
    fetch_data(123)  # TypeError λ°œμƒ
except Exception as e:
    print(f"였λ₯˜ λ°œμƒ: {e}")

βœ… νŠΉμ§•:

  • ν•¨μˆ˜ 메타데이터 보쑴
  • μ˜ˆμ™Έ μ²˜λ¦¬μ™€ μž¬μ‹œλ„
  • λ©”λͺ¨μ΄μ œμ΄μ…˜(캐싱)
  • νƒ€μž… 검증
  • λ‘œκΉ… 및 디버깅
  • μ‹€μ œ μœ μŠ€μΌ€μ΄μŠ€ 지원
  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° 접근법


7️⃣ λ©”μ„œλ“œ λ°μ½”λ ˆμ΄ν„°

클래슀 λ‚΄ λ©”μ„œλ“œμ— λ°μ½”λ ˆμ΄ν„°λ₯Ό μ μš©ν•˜λŠ” 방법이닀.

import functools

# μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œ λ°μ½”λ ˆμ΄ν„°
def require_auth(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        if not self.is_authenticated:
            raise PermissionError("인증이 ν•„μš”ν•©λ‹ˆλ‹€")
        return func(self, *args, **kwargs)
    return wrapper

# 클래슀 λ©”μ„œλ“œ λ°μ½”λ ˆμ΄ν„°
def class_method_decorator(func):
    @functools.wraps(func)
    def wrapper(cls, *args, **kwargs):
        print(f"클래슀 {cls.__name__}의 λ©”μ„œλ“œ 호좜")
        return func(cls, *args, **kwargs)
    return wrapper

# 정적 λ©”μ„œλ“œ λ°μ½”λ ˆμ΄ν„°
def static_method_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("정적 λ©”μ„œλ“œ 호좜")
        return func(*args, **kwargs)
    return wrapper

class User:
    def __init__(self, username, authenticated=False):
        self.username = username
        self.is_authenticated = authenticated
    
    @require_auth
    def view_profile(self):
        return f"{self.username}의 ν”„λ‘œν•„ 정보"
    
    @classmethod
    @class_method_decorator
    def create_admin(cls, username):
        user = cls(username, authenticated=True)
        user.is_admin = True
        return user
    
    @staticmethod
    @static_method_decorator
    def validate_username(username):
        return len(username) >= 3 and username.isalnum()

# λ©”μ„œλ“œ λ°μ½”λ ˆμ΄ν„° μ‚¬μš©
user = User("guest")
admin = User("admin", authenticated=True)

try:
    print(user.view_profile())  # PermissionError λ°œμƒ
except PermissionError as e:
    print(f"였λ₯˜: {e}")

print(admin.view_profile())  # admin의 ν”„λ‘œν•„ 정보

print(User.create_admin("superuser").is_admin)  # True

print(User.validate_username("user123"))  # True
print(User.validate_username("a"))  # False

βœ… νŠΉμ§•:

  • μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œ μž₯식
  • 클래슀 λ©”μ„œλ“œ μž₯식
  • 정적 λ©”μ„œλ“œ μž₯식
  • μ ‘κ·Ό μ œμ–΄ κ΅¬ν˜„
  • self/cls λ§€κ°œλ³€μˆ˜ 처리
  • 상속 ν˜Έν™˜μ„±
  • λ©”μ„œλ“œ μ’…λ₯˜λ³„ 적용 방법


8️⃣ λ°μ½”λ ˆμ΄ν„° λ””μžμΈ νŒ¨ν„΄κ³Ό κ³ κΈ‰ 기술

λ°μ½”λ ˆμ΄ν„° ν™œμš©μ— λŒ€ν•œ κ³ κΈ‰ ν…Œν¬λ‹‰κ³Ό λ””μžμΈ νŒ¨ν„΄μ΄λ‹€.

import functools
import inspect
from typing import Callable, TypeVar, cast, Any

# λ°μ½”λ ˆμ΄ν„° νŒ©ν† λ¦¬ νŒ¨ν„΄
class DecoratorFactory:
    @staticmethod
    def create_logging_decorator(log_level):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                print(f"[{log_level}] ν•¨μˆ˜ 호좜: {func.__name__}")
                return func(*args, **kwargs)
            return wrapper
        return decorator

debug = DecoratorFactory.create_logging_decorator("DEBUG")
info = DecoratorFactory.create_logging_decorator("INFO")
warning = DecoratorFactory.create_logging_decorator("WARNING")

@debug
def test_function():
    return "ν…ŒμŠ€νŠΈ μ™„λ£Œ"

# λ°μ½”λ ˆμ΄ν„° ν•©μ„± (Decorator Composition)
def compose_decorators(*decorators):
    def compose(f):
        for decorator in reversed(decorators):
            f = decorator(f)
        return f
    return compose

# νƒ€μž… 힌트λ₯Ό μœ μ§€ν•˜λŠ” λ°μ½”λ ˆμ΄ν„° (Python 3.10+)
T = TypeVar('T', bound=Callable[..., Any])

def preserves_signature(decorator: Callable[[T], Callable[..., Any]]) -> Callable[[T], T]:
    """λ°μ½”λ ˆμ΄ν„°κ°€ 원본 ν•¨μˆ˜μ˜ νƒ€μž… 힌트λ₯Ό μœ μ§€ν•˜λ„λ‘ ν•œλ‹€"""
    @functools.wraps(decorator)
    def wrapped_decorator(func: T) -> T:
        decorated_func = decorator(func)
        return cast(T, decorated_func)
    return wrapped_decorator

@preserves_signature
def timing(func: T) -> Callable[..., Any]:
    @functools.wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"ν•¨μˆ˜ {func.__name__} μ‹€ν–‰ μ‹œκ°„: {end - start:.4f}초")
        return result
    return wrapper

# 동적 λ°μ½”λ ˆμ΄ν„° 생성
def create_validator(validation_func, error_message):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if not validation_func(*args, **kwargs):
                raise ValueError(error_message)
            return func(*args, **kwargs)
        return wrapper
    return decorator

# λ°μ½”λ ˆμ΄ν„° μŠ€νƒ 검사
def get_decorator_stack(func):
    """ν•¨μˆ˜μ— 적용된 λ°μ½”λ ˆμ΄ν„° μŠ€νƒμ„ λΆ„μ„ν•œλ‹€"""
    result = []
    current = func
    while hasattr(current, '__wrapped__'):
        result.append(current.__name__)
        current = current.__wrapped__
    result.append(current.__name__)
    return result

# λ©”μ„œλ“œ 체이닝을 μœ„ν•œ λ°μ½”λ ˆμ΄ν„°
def chainable(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        func(self, *args, **kwargs)
        return self
    return wrapper

# μ‚¬μš© μ˜ˆμ‹œ
class QueryBuilder:
    def __init__(self):
        self.filters = []
        self.sorts = []
    
    @chainable
    def filter(self, **kwargs):
        self.filters.append(kwargs)
    
    @chainable
    def sort_by(self, field, ascending=True):
        self.sorts.append((field, ascending))
    
    def execute(self):
        return f"Query with filters: {self.filters} and sorts: {self.sorts}"

query = (QueryBuilder()
         .filter(status="active")
         .filter(age__gt=18)
         .sort_by("created_at", ascending=False)
         .execute())

print(query)

βœ… νŠΉμ§•:

  • λ°μ½”λ ˆμ΄ν„° νŒ©ν† λ¦¬ νŒ¨ν„΄
  • λ°μ½”λ ˆμ΄ν„° ν•©μ„±
  • νƒ€μž… 힌트 보쑴
  • 동적 λ°μ½”λ ˆμ΄ν„° 생성
  • λ°μ½”λ ˆμ΄ν„° μŠ€νƒ 뢄석
  • λ©”μ„œλ“œ 체이닝 κ΅¬ν˜„
  • κ³ κΈ‰ 섀계 기법


μ£Όμš” 팁

βœ… λͺ¨λ²” 사둀:

  • functools.wraps 항상 μ‚¬μš©ν•˜μ—¬ 메타데이터(docstring, 이름, μ„œλͺ…) 보쑴
  • λ¬Έμ„œν™” μœ μ§€ 및 λ°μ½”λ ˆμ΄ν„° μžμ²΄μ— docstring μΆ”κ°€
  • μ—λŸ¬ 처리 κ΅¬ν˜„ν•˜μ—¬ λͺ…ν™•ν•œ 였λ₯˜ λ©”μ‹œμ§€ 제곡
  • μ„±λŠ₯ κ³ λ € (λΆˆν•„μš”ν•œ μ˜€λ²„ν—€λ“œ ν”Όν•˜κΈ°)
  • 가독성 μœ μ§€ (λ³΅μž‘ν•œ λ°μ½”λ ˆμ΄ν„°λŠ” μž‘μ€ λ‹¨μœ„λ‘œ λΆ„ν•΄)
  • ν…ŒμŠ€νŠΈ μž‘μ„± (λ°μ½”λ ˆμ΄ν„°μ™€ μž₯μ‹λœ ν•¨μˆ˜ λͺ¨λ‘ ν…ŒμŠ€νŠΈ)
  • μž¬μ‚¬μš©μ„± κ³ λ € (λ²”μš©μ  κΈ°λŠ₯으둜 섀계)
  • νƒ€μž… νžŒνŠΈμ™€ ν˜Έν™˜μ„± μœ μ§€
  • 인자 μ—†λŠ” λ°μ½”λ ˆμ΄ν„°μ™€ 인자 μžˆλŠ” λ°μ½”λ ˆμ΄ν„° ꡬ뢄 이해
  • λ°μ½”λ ˆμ΄ν„° 단계별 디버깅 방법 μˆ™μ§€
  • 클래슀 기반과 ν•¨μˆ˜ 기반 λ°μ½”λ ˆμ΄ν„°μ˜ μ μ ˆν•œ μ‚¬μš© 상황 이해
  • κ³Όλ„ν•œ λ°μ½”λ ˆμ΄ν„° 쀑첩 ν”Όν•˜κΈ° (3-4개 이상 쀑첩 μ‹œ λ³΅μž‘λ„ 증가)
  • ν•„μš”ν•œ 경우 inspection λͺ¨λ“ˆλ‘œ ν•¨μˆ˜ μ„œλͺ… 뢄석


⚠️ **GitHub.com Fallback** ⚠️