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
@How Decorators Work
pythondef 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
pythonimport 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
pythonimport 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
pythonimport 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
| Decorator | Use Case |
|---|---|
text | Method that doesn't need text |
text | Method that receives the class, not instance |
text | Getter for computed attributes |
text | Memoisation for pure functions |
text | Auto-generate text text |
Interview tip: Decorators are the Python equivalent of middleware — perfect for cross-cutting concerns like logging, auth, retries, and caching in LLM pipelines.