If you'd like third party's account why ``attrs`` is great, have a look at Glyph's `The One Python Library Everyone Needs <https://glyph.twistedmatrix.com/2016/08/attrs.html>`_!
Imagine you have a function that takes or returns a tuple.
Especially if you use tuple unpacking (eg. ``x, y = get_point()``), adding additional data means that you have to change the invocation of that function *everywhere*.
Adding an attribute to a class concerns only those who actually care about that attribute.
- Since they are a subclass of tuples, ``namedtuple``\ s have a length and are both iterable and indexable.
That's not what you'd expect from a class and is likely to shadow subtle typo bugs.
- Iterability also implies that it's easy to accidentally unpack a ``namedtuple`` which leads to hard-to-find bugs. [#iter]_
-``namedtuple``\ s have their methods *on your instances* whether you like it or not. [#pollution]_
-``namedtuple``\ s are *always* immutable.
Not only does that mean that you can't decide for yourself whether your instances should be immutable or not, it also means that if you want to influence your class' initialization (validation? default values?), you have to implement :meth:`__new__() <object.__new__>` which is a particularly hacky and error-prone requirement for a very common problem. [#immutable]_
- To attach methods to a ``namedtuple`` you have to subclass it.
And if you follow the standard library documentation's recommendation of::
you end up with a class that has *two*``Point``\ s in its :attr:`__mro__ <class.__mro__>`: ``[<class 'point.Point'>, <class 'point.Point'>, <type 'tuple'>, <type 'object'>]``.
That's not only confusing, it also has very practical consequences:
for example if you create documentation that includes class hierarchies like `Sphinx's autodoc <http://www.sphinx-doc.org/en/stable/ext/autodoc.html>`_ with ``show-inheritance``.
Again: common problem, hacky solution with confusing fallout.
If you want a *tuple with names*, by all means: go for a ``namedtuple``. [#perf]_
But if you want a class with methods, you're doing yourself a disservice by relying on a pile of hacks that requires you to employ even more hacks as your requirements expand.
Other than that, ``attrs`` also adds nifty features like validators, converters, and (mutable!) default values.
..[#immutable]``attrs`` offers *optional* immutability through the ``frozen`` keyword.
..[#perf] Although ``attrs`` would serve you just as well!
Since both employ the same method of writing and compiling Python code for you, the performance penalty is negligible at worst and in some cases ``attrs`` is even faster if you use ``slots=True`` (which is generally a good idea anyway).
.._behaving like a tuple: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
They are the result of the Python community's `wish <https://mail.python.org/pipermail/python-ideas/2017-May/045618.html>`_ to have an easier way to write classes in the standard library that doesn't carry the problems of ``namedtuple``\ s.
To that end, ``attrs`` and its developers were involved in the PEP process and while we may disagree with some minor decisions that have been made, it's a fine library and if it stops you from abusing ``namedtuple``\ s, they are a huge win.
Nevertheless, there are still reasons to prefer ``attrs`` over Data Classes whose relevancy depends on your circumstances:
- Data Classes are intentionally less powerful than ``attrs``.
There is a long list of features that were sacrificed for the sake of simplicity and while the most obvious ones are validators, converters, and ``__slots__``, it permeates throughout all APIs.
On the other hand, Data Classes currently do not offer any significant feature that ``attrs`` doesn't already have.
-``attrs`` can and will move faster.
We are not bound to any release schedules and we have a clear deprecation policy.
One of the `reasons <https://www.python.org/dev/peps/pep-0557/#why-not-just-use-attrs>`_ to not vendor ``attrs`` in the standard library was to not impede ``attrs``'s future developement.
If you have a dict, it maps something to something else.
You should be able to add and remove values.
``attrs`` lets you be specific about those expectations; a dictionary does not.
It gives you a named entity (the class) in your code, which lets you explain in other places whether you take a parameter of that class or return a value of that class.
In other words: if your dict has a fixed and known set of keys, it is an object, not a hash.
If you don't care and like typing, we're not gonna stop you.
However it takes a lot of bias and determined rationalization to claim that ``attrs`` raises the mental burden on a project given how difficult it is to find the important bits in a hand-written class and how annoying it is to ensure you've copy-pasted your code correctly over all your classes.
In any case, if you ever get sick of the repetitiveness and drowning important code in a sea of boilerplate, ``attrs`` will be waiting for you.