pvdocs /Python/Decorators

Decorators

A decorator is a function that takes a function as input, wraps it with extra behavior, and returns a new function.

Basic Decorator

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

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

greet("Alice")
# Before
# Hello, Alice!
# After

@my_decorator is syntactic sugar for greet = my_decorator(greet).

Preserving Metadata with functools.wraps

Without @wraps, the wrapper hides the original function's name and docstring.

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

Decorator with Arguments

Add an extra outer layer:

def repeat(n):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def say_hi():
    print("Hi!")

say_hi()  # Hi! Hi! Hi!

Stacking Decorators

Applied bottom-up:

@decorator_a
@decorator_b
def func(): ...

# equivalent to: func = decorator_a(decorator_b(func))

Class Decorator

class Timer:
    def __init__(self, func):
        wraps(func)(self)
        self.func = func

    def __call__(self, *args, **kwargs):
        import time
        start  = time.perf_counter()
        result = self.func(*args, **kwargs)
        print(f"{self.func.__name__} took {time.perf_counter() - start:.4f}s")
        return result

Built-in Decorators

class MyClass:
    class_var = 0

    @staticmethod
    def utility():          # no self or cls — just a namespaced function
        return "static"

    @classmethod
    def from_string(cls, s):  # receives the class, not the instance
        return cls()

    @property
    def value(self):        # call as obj.value, not obj.value()
        return self._value

    @value.setter
    def value(self, v):
        self._value = v

Common Practical Decorators

import functools, time

# Memoization / cache
@functools.lru_cache(maxsize=128)
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

# Retry on exception
def retry(times=3):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == times - 1:
                        raise
        return wrapper
    return decorator

Key Interview Points