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.
..[#history] The word is that ``namedtuple``\ s were added to the Python standard library as a way to make tuples in return values more readable.
And indeed that is something you see throughout the standard library.
Looking at what for the makers of ``namedtuple``\ s use it themselves is a good guideline for deciding on your own use cases.
..[#pollution]``attrs`` only adds a single attribute: ``__attrs_attrs__`` for introspection.
All helpers are functions in the ``attr`` package.
Since they take the instance as first argument, you can easily attach them to your classes under a name of your own choice.
..[#iter]:func:`attr.astuple` can be used to get that behavior in ``attrs`` on *explicit demand*.
..[#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
``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.