Overview¶
In order to fulfill its ambitious goal of bringing back the joy to writing classes, it gives you a class decorator and a way to declaratively define the attributes on that class:
>>> from attrs import asdict, define, make_class, Factory
>>> @define
... class SomeClass:
... a_number: int = 42
... list_of_numbers: list[int] = Factory(list)
...
... def hard_math(self, another_number):
... return self.a_number + sum(self.list_of_numbers) * another_number
>>> sc = SomeClass(1, [1, 2, 3])
>>> sc
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
>>> sc.hard_math(3)
19
>>> sc == SomeClass(1, [1, 2, 3])
True
>>> sc != SomeClass(2, [3, 2, 1])
True
>>> asdict(sc)
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}
>>> SomeClass()
SomeClass(a_number=42, list_of_numbers=[])
>>> C = make_class("C", ["a", "b"])
>>> C("foo", "bar")
C(a='foo', b='bar')
After declaring your attributes attrs gives you:
a concise and explicit overview of the class’s attributes,
a nice human-readable
__repr__,a equality-checking methods,
an initializer,
and much more,
without writing dull boilerplate code again and again and without runtime performance penalties.
Hate type annotations!?
No problem!
Types are entirely optional with attrs.
Simply assign attrs.field() to the attributes instead of annotating them with types.
This example uses attrs’s modern APIs that have been introduced in version 20.1.0, and the attrs package import name that has been added in version 21.3.0.
The classic APIs (@attr.s, attr.ib, plus their serious business aliases) and the attr package import name will remain indefinitely.
Please check out On The Core API Names for a more in-depth explanation.
Data Classes¶
On the tin, attrs might remind you of dataclasses (and indeed, dataclasses are a descendant of attrs).
In practice it does a lot more and is more flexible.
For instance it allows you to define special handling of NumPy arrays for equality checks, or allows more ways to plug into the initialization process.
For more details, please refer to our comparison page.
Philosophy¶
- It’s about regular classes.
attrsis for creating well-behaved classes with a type, attributes, methods, and everything that comes with a class. It can be used for data-only containers likenamedtuples ortypes.SimpleNamespacebut they’re just a sub-genre of whatattrsis good for.- The class belongs to the users.
You define a class and
attrsadds static methods to that class based on the attributes you declare. The end. It doesn’t add metaclasses. It doesn’t add classes you’ve never heard of to your inheritance tree. Anattrsclass in runtime is indistinguishable from a regular class: because it is a regular class with a few boilerplate-y methods attached.- Be light on API impact.
As convenient as it seems at first,
attrswill not tack on any methods to your classes except for the dunder ones. Hence all the useful tools that come withattrslive in functions that operate on top of instances. Since they take anattrsinstance as their first argument, you can attach them to your classes with one line of code.- Performance matters.
attrsruntime impact is very close to zero because all the work is done when the class is defined. Once you’re instantiating it,attrsis out of the picture completely.- No surprises.
attrscreates classes that arguably work the way a Python beginner would reasonably expect them to work. It doesn’t try to guess what you mean because explicit is better than implicit. It doesn’t try to be clever because software shouldn’t be clever.
Check out How Does It Work? if you’d like to know how it achieves all of the above.
What attrs Is Not¶
attrs does not invent some kind of magic system that pulls classes out of its hat using meta classes, runtime introspection, and shaky interdependencies.
All attrs does is:
take your declaration,
write dunder methods based on that information,
and attach them to your class.
It does nothing dynamic at runtime, hence zero runtime overhead. It’s still your class. Do with it as you please.