A decorator is a function that takes another function and returns a modified version, used to add behavior (logging, timing, retry, skipping) without changing the wrapped function's code. The @decorator syntax applies it.
@my_decorator above a def is sugar for func = my_decorator(func). A decorator typically defines an inner wrapper(*args, **kwargs) that runs extra logic before/after calling the original, then returns wrapper. Use functools.wraps to preserve the original name/docstring. Decorators power much of the testing world: pytest's @pytest.fixture, @pytest.mark.parametrize, @pytest.mark.skip, and custom @retry or @timed wrappers. Understanding them explains how pytest markers and fixtures work under the hood.
A retry decorator for flaky steps: @retry(times=3) def fetch(): ... re-runs fetch up to 3 times on failure — reusable across any test step by just adding the decorator.
After decorating a function, help(func) shows 'wrapper' instead of the real name. What's missing?