attrs
attrs is a Python package that auto-generates dunder methods (__init__, __repr__, __eq__, etc.) for your classes via a declarative decorator, eliminating boilerplate. The modern API (`attrs.define`, `attrs.field`, `attrs.frozen`) was stabilised in v20.1.0 and the `attrs` import namespace was added in v21.3.0. The current version is 26.1.0 (released 2026). Releases are frequent and roughly follow a quarterly cadence; Python >=3.9 is required.
Warnings
- breaking @define enables slots=True by default. Slotted classes have no __dict__, so arbitrary attribute assignment (e.g. during monkeypatching or pickle with non-attrs base-class state) will raise AttributeError.
- breaking When using @define, converters run by default on setattr (on_setattr=[setters.convert, setters.validate]), not just at __init__ time. Code that relied on bypassing converter logic after construction will break silently or raise.
- breaking kw_only=True on a class-level @define no longer forces kw_only on individual attributes that explicitly set kw_only=False (changed in 25.4.0). Subclasses with required positional args following a kw_only=False default field now raise a TypeError at class definition time.
- gotcha Mutable defaults (list, dict, set) must not be passed as `default=<value>`. All instances will share the same object, causing cross-instance state mutation.
- gotcha Subclassing a @define class and adding a mandatory (no-default) attribute after a parent attribute that has a default raises ValueError at class definition time: 'No mandatory attributes allowed after an attribute with a default value or factory.'
- gotcha Slotted classes replace the original class object at decoration time. __init_subclass__ is therefore called twice for slotted subclasses (once for the original, once for the replacement), and metaclass features that depend on class identity can break silently.
- gotcha In frozen classes, you cannot assign to self in __attrs_post_init__ via normal attribute assignment — it raises FrozenInstanceError.
Install
-
pip install attrs
Imports
- define
from attrs import define, field, Factory
- frozen
from attrs import frozen
- asdict
from attrs import asdict
- fields
from attrs import fields
- Factory
from attrs import Factory some_field: list = field(factory=list) # or Factory(list)
Quickstart
from attrs import define, field, frozen, Factory, asdict, evolve, validators
# Modern API — slots=True by default, converters run on setattr
@define
class Point:
x: float
y: float
tags: list[str] = field(factory=list) # safe mutable default
@x.validator
def _positive_x(self, attribute, value):
if value < 0:
raise ValueError(f"x must be non-negative, got {value}")
p = Point(1.0, 2.0)
p.tags.append("origin-area")
print(p) # Point(x=1.0, y=2.0, tags=['origin-area'])
print(asdict(p)) # {'x': 1.0, 'y': 2.0, 'tags': ['origin-area']}
# Frozen (immutable) class — use evolve() to get a modified copy
@frozen
class Config:
host: str = "localhost"
port: int = 8080
cfg = Config()
new_cfg = evolve(cfg, port=9090)
print(new_cfg) # Config(host='localhost', port=9090)
# Nested serialisation
@define
class Server:
config: Config
name: str
srv = Server(config=cfg, name="primary")
print(asdict(srv))