Decorators
Decorators: wrapping behavior without modifying code
Section titled “Decorators: wrapping behavior without modifying code”TypeScript has decorators too, but they are an experimental feature, tied to classes, and their behavior has shifted across TypeScript and ECMAScript proposals. Python’s decorators are stable, simpler, and work on any function — not just class methods. They are used so pervasively in Python frameworks (Flask routes, FastAPI endpoints, Celery tasks, dataclasses.dataclass) that you will encounter them in almost every Python codebase.
A decorator is just a function that takes a function and returns a new function. The @name syntax is shorthand for func = name(func).
Wrapping a function
Section titled “Wrapping a function”The canonical pattern is wrapping an existing function to add behavior before and after the call. On the TypeScript side, achieving the same effect on a plain function requires either a higher-order function called manually or a class decorator that only works on methods.
// TypeScript: class method decorator (experimental, stage 3)// Works on class methods, not plain functionsfunction log(target: any, key: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${key}...`); const result = original.apply(this, args); console.log(`${key} finished.`); return result; }; return descriptor;}
class Greeter { @log greet(name: string) { console.log(`Hello, ${name}!`); }}
// For a plain function you must wrap it manually:function withLog<T extends (...args: any[]) => any>(fn: T): T { return ((...args: any[]) => { console.log(`Calling ${fn.name}...`); const result = fn(...args); console.log(`${fn.name} finished.`); return result; }) as T;}
const greet = withLog((name: string) => console.log(`Hello, ${name}!`));greet("Alice");# Python: decorators work on any function, syntax is @nameimport functools
def timer_decorator(func): @functools.wraps(func) # preserves __name__, __doc__, etc. def wrapper(*args, **kwargs): print(f"Calling {func.__name__}...") result = func(*args, **kwargs) print(f"{func.__name__} finished.") return result return wrapper
@timer_decorator # equivalent to: greet = timer_decorator(greet)def greet(name): print(f"Hello, {name}!")
greet("Alice")# Output:# Calling greet...# Hello, Alice!# greet finished.Decorators with arguments: decorator factories
Section titled “Decorators with arguments: decorator factories”When you need to pass configuration to a decorator, you add an outer function that accepts the arguments and returns the actual decorator. This is called a decorator factory.
def repeat(n): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for _ in range(n): func(*args, **kwargs) return wrapper return decorator
@repeat(3) # repeat(3) returns decorator; decorator(say_hi) returns wrapperdef say_hi(): print("Hi!")
say_hi() # prints "Hi!" three timesThe three-level nesting (factory -> decorator -> wrapper) is the standard pattern whenever a decorator needs parameters.
Try it
Section titled “Try it”import functools
def timer_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__}...") result = func(*args, **kwargs) print(f"{func.__name__} finished.") return result return wrapper
@timer_decoratordef greet(name): print(f"Hello, {name}!")
greet("Alice")
def repeat(n): def decorator(func): @functools.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()Loading Python runtime (first run only)…