On The Core API Names¶
You may be surprised seeing attrs classes being created using attrs.define()
and with type annotated fields, instead of attr.s()
and attr.ib()
.
Or, you wonder why the web and talks are full of this weird attr.s
and attr.ib
– including people having strong opinions about it and using attr.attrs
and attr.attrib
instead.
And what even is attr.dataclass
that’s not documented but commonly used!?
TL;DR¶
We recommend our modern APIs for new code:
attrs.define()
to define a new class,attrs.mutable()
is an alias forattrs.define()
,attrs.frozen()
is an alias fordefine(frozen=True)
and
attrs.field()
to define an attribute.
They have been added in attrs 20.1.0, they are expressive, and they have modern defaults like slots and type annotation awareness switched on by default.
Sometimes, they’re referred to as next-generation or NG APIs.
As of attrs 21.3.0 you can also import them from the attrs
package namespace.
The traditional, or OG, APIs attr.s()
/ attr.ib()
, their serious-business aliases attr.attrs
/ attr.attrib
, and the never-documented, but popular attr.dataclass
easter egg will stay forever.
attrs will never force you to use type annotations.
A Short History Lesson¶
At this point, attrs is an old project. It had its first release in April 2015 – back when most Python code was on Python 2.7 and Python 3.4 was the first Python 3 release that showed promise. attrs was always Python 3-first, but type annotations came only into Python 3.5 that was released in September 2015 and were largely ignored until years later.
At this time, if you didn’t want to implement all the dunder methods, the most common way to create a class with some attributes on it was to subclass a collections.namedtuple
, or one of the many hacks that allowed you to access dictionary keys using attribute lookup.
But attrs history goes even a bit further back, to the now-forgotten characteristic that came out in May 2014 and already used a class decorator, but was overall too unergonomic.
In the wake of all of that, Glyph and Hynek came together on IRC and brainstormed how to take the good ideas of characteristic, but make them easier to use and read. At this point the plan was not to make attrs what it is now – a flexible class-building kit. All we wanted was an ergonomic little library to succinctly define classes with attributes.
Under the impression of the unwieldy characteristic
name, we went to the other side and decided to make the package name part of the API, and keep the API functions very short.
This led to the infamous attr.s()
and attr.ib()
which some found confusing and pronounced it as “attr dot s” or used a singular @s
as the decorator.
But it was really just a way to say attrs
and attrib
[1].
Some people hated this cutey API from day one, which is why we added aliases for them that we called serious business: @attr.attrs
and attr.attrib()
.
Fans of them usually imported the names and didn’t use the package name in the first place.
Unfortunately, the attr
package name started creaking the moment we added attr.Factory
, since it couldn’t be morphed into something meaningful in any way.
A problem that grew worse over time, as more APIs and even modules were added.
But overall, attrs in this shape was a huge success – especially after Glyph’s blog post The One Python Library Everyone Needs in August 2016 and pytest adopting it.
Being able to just write:
@attr.s
class Point:
x = attr.ib()
y = attr.ib()
was a big step for those who wanted to write small, focused classes.
Dataclasses Enter The Arena¶
A big change happened in May 2017 when Hynek sat down with Guido van Rossum and Eric V. Smith at PyCon US 2017.
Type annotations for class attributes have just landed in Python 3.6 and Guido felt like it would be a good mechanic to introduce something similar to attrs to the Python standard library.
The result, of course, was PEP 557[2] which eventually became the dataclasses
module in Python 3.7.
attrs at this point was lucky to have several people on board who were also very excited about type annotations and helped implement it; including a Mypy plugin.
And so it happened that attrs shipped the new method of defining classes more than half a year before Python 3.7 – and thus dataclasses
– were released.
Due to backwards-compatibility concerns, this feature is off by default in the attr.s()
decorator and has to be activated using @attr.s(auto_attribs=True)
, though.
As a little easter egg and to save ourselves some typing, we’ve also added an alias called attr.dataclass
that just set auto_attribs=True
.
It was never documented, but people found it and used it and loved it.
Over the next months and years it became clear that type annotations have become the popular way to define classes and their attributes. However, it has also become clear that some people viscerally hate type annotations. We’re determined to serve both.
attrs TNG¶
Over its existence, attrs never stood still. But since we also greatly care about backwards-compatibility and not breaking our users’ code, many features and niceties have to be manually activated.
That is not only annoying, it also leads to the problem that many of attrs’s users don’t even know what it can do for them.
We’ve spent years alone explaining that defining attributes using type annotations is in no way unique to dataclasses
.
Finally we’ve decided to take the Go route:
Instead of fiddling with the old APIs – whose names felt anachronistic anyway – we’d define new ones, with better defaults.
So in July 2018, we looked for better names and came up with attr.define()
, attr.field()
, and friends.
Then in January 2019, we started looking for inconvenient defaults that we now could fix without any repercussions.
These APIs proved to be very popular, so we’ve finally changed the documentation to them in November of 2021.
All of this took way too long, of course. One reason is the COVID-19 pandemic, but also our fear to fumble this historic chance to fix our APIs.
Finally, in December 2021, we’ve added the attrs package namespace.
We hope you like the result:
from attrs import define
@define
class Point:
x: int
y: int