Decorators are callables that take a function and return another function (or callable), usually to add behavior—logging, timing, validation, retries—without changing the original function’s code. The @decorator syntax is shorthand for “replace this function with the result of calling the decorator on it.” Understanding closures and *args and **kwargs helps when writing decorators that work with any function signature.

What they are

A decorator is a function that accepts a function and returns a new function. When you write @my_decorator above def f(): ..., Python does f = my_decorator(f). Calls to f() then run the returned wrapper, which can do work before and after (and around) calling the original function.

Simple decorator (no arguments)

def my_decorator(func):
    def wrapper():
        print("Before function")
        func()
        print("After function")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# Before function
# Hello!
# After function

Here wrapper is a closure that captures func. Returning wrapper makes say_hello refer to it, so say_hello() runs wrapper().

Decorator that preserves the original function’s arguments

To wrap functions that take arguments, the wrapper must accept and forward them. Use *args, **kwargs and return the original function’s return value:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Calling function...")
        result = func(*args, **kwargs)
        print("Function finished")
        return result
    return wrapper

@my_decorator
def add(a, b):
    return a + b

print(add(3, 5))  # Calling... / Function finished / 8

Common use: timing

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.2f}s")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "Done"

slow_function()  # slow_function took 1.00s

When to use decorators

  • Cross-cutting behavior: logging, timing, retries, or validation applied to many functions without repeating code.
  • Wrappers that must work with any signature: use *args, **kwargs and return func(*args, **kwargs).
  • Framework hooks: many libraries use decorators for routes, auth, or registration (e.g. Flask @app.route).

Next steps: Core Concepts · Closures · *args and **kwargs · Functions