Skip to content

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).

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
// TypeScript: class method decorator (experimental, stage 3)
// Works on class methods, not plain functions
function 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
# Python: decorators work on any function, syntax is @name
import 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 wrapper
def say_hi():
print("Hi!")
say_hi() # prints "Hi!" three times

The three-level nesting (factory -> decorator -> wrapper) is the standard pattern whenever a decorator needs parameters.

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_decorator
def 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()
When does a Python decorator run?
What does `@functools.wraps(func)` do inside a decorator?
What is the equivalent plain Python of `@my_decorator` applied to `def foo(): ...`?