An attempt at catharsis.
Remember how long you’ve been putting this off, how many extensions the gods gave you, and you didn’t use them.
This is a deeply personal blog post about the most influential project I’ve ever created: attrs, the progenitor of modern Python class utilities – including dataclasses. I’m trying to retell it’s history from my perspective and how I’m leading it into the future today.
You might detect hints of bitterness in this post. That’s because it also deals with feelings, and there’s been a lot of those. You see, attrs and I have been subject to constant erasure and revisionism, bordering on abuse.
Historic Context
The origin of attrs is an early morning/late night (depending on the perspective) IRC session with my friend Glyph. At that point, its predecessor characteristic1 has been out for a while and although it was very useful, I was feeling some frustrations. Most notably the verbose name that is annoying to type and the way attributes are defined (arguments to the decorator, instead in the class body).
We had some really wild ideas I don’t even dare to reproduce after the amount of discomfort some people feel about the “cutesy” attr.s
and attr.ib
names that just mean “attr[ibutes]s” and “attrib[ute]” respectively.2
It’s important to understand that we thought of a much narrower scope for the project than what it is today. So while I had some discomfort from using attr.Factory
, I begrudgingly made peace with it because I didn’t expect the module name to be used more than for @attr.s/attr.ib
:
import attr
@attr.s
class Point:
x = attr.ib()
y = attr.ib()
Back then, when I tried to register attr
on PyPI, it turned out that there already is a project of that name3. While that was a constant annoyance and some people were really jerks about it4, now I’m glad things worked out the way they did.
Because today, it allows us to break backwards-compatibility without breaking anyone’s code. #foreshadow
Things Happened
After some time of community reluctancy about ostensible magic5, attrs took off in 2016, fueled by a blog post by Glyph titled The One Python Library Everyone Needs.
It’s hard to imagine today, but back then, if you wanted a class that carries some attributes, you had to either write walls of boilerplate code or employ hacks like subclassing namedtuples6:
class Point(namedtuple('Point', ['x', 'y'])):
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
attrs changed that and suddenly creating a new class for your abstraction stopped being chore. This meant less God objects, less nondescript tuples, and less free-form dicts.
attrs became so good they couldn’t ignore it7.
Types Enter the Arena
And so in 2017 people started asking for something similar in the Python standard library, which lead to an email from Guido van Rossum to me, after I’ve announced attrs 17.1.0 and was on my way to PyCon US 20178. He asked me to meet up with him and Eric V. “f-strings” Smith to talk about “the future of attrs and how/whether we might provide some of its functionality in future stdlib versions”.
This lead to a long and productive meeting in the hallway of the Portland convention center (followed by several months of online cooperation) and became the inception of the dataclasses
module in Python 3.79.
At that time, type hints in Python weren’t a topic that was a regular source of hot takes and flame wars on Twitter. I didn’t use them myself at all. But in hindsight it is clear that dataclasses
’s approach of using type hints for metadata that is used at runtime – that has been adopted by other class-building toolkits ever since – had a strong influence on the adoption of type h