Concept #19Mediumpython-for-gen-ai

Explain decorators in Python. How would you use them in an LLM application?

#gen-ai#python

Answer

Decorators in Python

A decorator is a function that wraps another function to modify or extend its behaviour — without changing the original function's code. They are applied using the

text
@
syntax.

How Decorators Work

python
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before the function")
        result = func(*args, **kwargs)
        print("After the function")
        return result
    return wrapper

@my_decorator
def greet(name):
    print(f"Hello, {name}")

greet("Alice")
# Before the function
# Hello, Alice
# After the function

Practical Decorators for LLM Applications

1. Retry decorator for flaky API calls

python
import time
import functools
from openai import RateLimitError, APIError

def retry(max_attempts=3, delay=1.0, backoff=2.0, exceptions=(RateLimitError, APIError)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            attempt = 0
            current_delay = delay
            while attempt < max_attempts:
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    attempt += 1
                    if attempt == max_attempts:
                        raise
                    print(f"Attempt {attempt} failed: {e}. Retrying in {current_delay}s...")
                    time.sleep(current_delay)
                    current_delay *= backoff
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2.0)
def call_llm(prompt: str) -> str:
    from openai import OpenAI
    client = OpenAI()
    return client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}]
    ).choices[0].message.content

2. Timing/observability decorator

python
import time
import logging
import functools

logger = logging.getLogger(__name__)

def track_latency(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            result = func(*args, **kwargs)
            duration = time.perf_counter() - start
            logger.info(f"{func.__name__} completed in {duration:.3f}s")
            return result
        except Exception as e:
            duration = time.perf_counter() - start
            logger.error(f"{func.__name__} failed after {duration:.3f}s: {e}")
            raise
    return wrapper

@track_latency
def embed_text(text: str) -> list[float]:
    # ... embedding logic
    pass

3. Caching decorator

python
import hashlib
import json
import functools

def lru_cache_llm(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(prompt: str, **kwargs):
        # Create cache key from prompt + kwargs
        key = hashlib.md5(json.dumps({"prompt": prompt, **kwargs}).encode()).hexdigest()
        if key not in cache:
            cache[key] = func(prompt, **kwargs)
        return cache[key]
    return wrapper

@lru_cache_llm
def cached_completion(prompt: str, model: str = "gpt-4o") -> str:
    # Expensive LLM call
    pass

Common Built-in Decorators

DecoratorUse Case
text
@staticmethod
Method that doesn't need
text
self
text
@classmethod
Method that receives the class, not instance
text
@property
Getter for computed attributes
text
@functools.lru_cache
Memoisation for pure functions
text
@dataclass
Auto-generate
text
__init__
,
text
__repr__
etc.

Interview tip: Decorators are the Python equivalent of middleware — perfect for cross-cutting concerns like logging, auth, retries, and caching in LLM pipelines.