Python decorators: functions wrapping functions
Every time you write @dataclass above a class or @field_validator('name') above a method, you are using a decorator — and the @ is not magic. It is syntactic sugar for passing a function (or class) through another callable that wraps or replaces it.
@shout above def greet(name) is exactly equivalent to writing greet = shout(greet) after the definition. The decorator shout receives the original function, and whatever it returns is what the name greet now refers to. The standard pattern: an outer function receives fn, defines an inner wrapper(*args, **kwargs) that does something before or after calling fn, and returns wrapper. The returned wrapper replaces the original function.
The most common bug: forgetting return wrapper from the outer function. Without it, the decorated name becomes None, and calling it raises TypeError: 'NoneType' object is not callable. It is a silent, confusing failure because the decorator runs fine — it just returns nothing.
Use functools.wraps(fn) on the wrapper to copy the original function's __name__, __doc__, and other metadata. Without it, help(greet) shows wrapper instead of greet, and debuggers show the wrong name in tracebacks. This is a small detail that matters in production.
These concepts connect directly to what you have already used: @dataclass is a class decorator that rewrites the class body to inject __init__, __repr__, etc. @classmethod is a method decorator. @field_validator in Pydantic registers your method as a validator on the model schema.
Functools docs: [1], Python glossary on decorators: [2].
Sources
Tasks
Card Info
- Topic: Data Science Praktikum
- Difficulty: Beginner
- Completed: 0 users