Python Cheatsheet
Decorators
Use this Python reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
What Decorators Are
A decorator is syntactic sugar for passing a function (or class) through another function and rebinding the name.
@decorator def func(): pass # Exactly equivalent to: def func(): pass func = decorator(func)
Basic Function Decorator
import functools def my_decorator(func): @functools.wraps(func) # preserves __name__, __doc__, __module__, etc. def wrapper(*args, **kwargs): print("before") result = func(*args, **kwargs) print("after") return result return wrapper @my_decorator def greet(name): """Say hello.""" print(f"Hello, {name}!") greet("Alice") # before # Hello, Alice! # after greet.__name__ # "greet" (thanks to @wraps) greet.__doc__ # "Say hello."
Decorator with Arguments
Add an outer layer to accept parameters.
def repeat(n): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def say(msg): print(msg) say("hi") # prints "hi" three times
Common Built-in Decorators
class MyClass: counter = 0 @classmethod def create(cls): # receives class as first arg return cls() @staticmethod def helper(): # no implicit first arg return 42 @property def value(self): # computed attribute return self._value @value.setter def value(self, v): self._value = v @value.deleter def value(self): del self._value def __init_subclass__(cls, **kwargs): # called when subclassed super().__init_subclass__(**kwargs)
functools Decorators
from functools import lru_cache, cache, cached_property, wraps, total_ordering # Memoization @lru_cache(maxsize=128) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) fib.cache_info() # CacheInfo(hits=..., misses=..., maxsize=128, currsize=...) fib.cache_clear() @cache # unbounded memoization (Python 3.9+) def expensive(x): ... # Cached property — computed once, then stored as instance attribute class DataLoader: @cached_property def data(self): # computed only on first access return load_from_disk() # total_ordering — fill in comparison methods automatically @total_ordering class Temperature: def __init__(self, degrees): self.degrees = degrees def __eq__(self, other): return self.degrees == other.degrees def __lt__(self, other): return self.degrees < other.degrees # __le__, __gt__, __ge__ are derived automatically
Timing and Debugging Decorators
import time import functools def timer(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = time.perf_counter() - start print(f"{func.__name__} took {elapsed:.4f}s") return result return wrapper def debug(func): @functools.wraps(func) def wrapper(*args, **kwargs): args_repr = [repr(a) for a in args] kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] print(f"Calling {func.__name__}({', '.join(args_repr + kwargs_repr)})") result = func(*args, **kwargs) print(f"{func.__name__} returned {result!r}") return result return wrapper
Retry Decorator
import time import functools def retry(times=3, delay=1.0, exceptions=(Exception,)): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for attempt in range(times): try: return func(*args, **kwargs) except exceptions as e: if attempt == times - 1: raise time.sleep(delay) return wrapper return decorator @retry(times=5, delay=0.5, exceptions=(ConnectionError, TimeoutError)) def fetch(url): ...
Class-Based Decorators
Classes with __call__ can be decorators.
class Validate: def __init__(self, func): functools.update_wrapper(self, func) self.func = func def __call__(self, *args, **kwargs): if not args: raise ValueError("at least one argument required") return self.func(*args, **kwargs) @Validate def process(data): return data # Class with arguments class Retry: def __init__(self, times=3): self.times = times def __call__(self, func): @functools.wraps(func) def wrapper(*args, **kwargs): for _ in range(self.times): try: return func(*args, **kwargs) except Exception: pass raise RuntimeError("all retries failed") return wrapper @Retry(times=5) def flaky(): ...
Stacking Decorators
Applied bottom-up (closest to the function first).
@decorator_a @decorator_b @decorator_c def func(): pass # Equivalent to: func = decorator_a(decorator_b(decorator_c(func)))
Decorator for Classes
# Decorating a class (not a method) def add_repr(cls): def __repr__(self): attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items()) return f"{cls.__name__}({attrs})" cls.__repr__ = __repr__ return cls @add_repr class Config: def __init__(self, host, port): self.host = host self.port = port Config("localhost", 8080) # Config(host='localhost', port=8080) # Singleton decorator def singleton(cls): instances = {} @functools.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 Database: def __init__(self): ...
contextlib Decorator Helpers
from contextlib import contextmanager @contextmanager def timer_ctx(): import time start = time.perf_counter() yield print(f"Elapsed: {time.perf_counter() - start:.4f}s") with timer_ctx(): expensive_operation()
Preserving Metadata (@wraps)
Without @wraps, the wrapper replaces metadata:
def bad_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @bad_decorator def original(): """Original docstring.""" pass original.__name__ # "wrapper" — WRONG original.__doc__ # None — WRONG # Fix: always use @functools.wraps(func) on wrapper def good_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper