| # On The Core API Names | |
| You may be surprised seeing *attrs* classes being created using {func}`attrs.define` and with type annotated fields, instead of {func}`attr.s` and {func}`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: | |
| % {func} syntax does currently not work for `.. function` definitions. | |
| - {func}`attrs.define` to define a new class, | |
| - [`attrs.mutable()`](attrs.mutable) is an alias for {func}`attrs.define`, | |
| - [`attrs.frozen()`](attrs.frozen) is an alias for `define(frozen=True)` | |
| - and {func}`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 {func}`attr.s` / {func}`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](https://peps.python.org/pep-0484/) 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 {term}`dunder methods`, the most common way to create a class with some attributes on it was to subclass a {obj}`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*](https://github.com/hynek/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](https://github.com/glyph) and [Hynek](https://github.com/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 {func}`attr.s` and {func}`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`[^attr]. | |
| [^attr]: We considered calling the PyPI package just `attr` too, but the name was already taken by an *ostensibly* inactive [package on PyPI](https://pypi.org/project/attr/#history). | |
| 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*](https://glyph.twistedmatrix.com/2016/08/attrs.html) in August 2016 and [*pytest*](https://docs.pytest.org/) 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](https://en.wikipedia.org/wiki/Guido_van_Rossum) and [Eric V. Smith](https://github.com/ericvsmith) at PyCon US 2017. | |
| Type annotations for class attributes have [just landed](https://peps.python.org/pep-0526/) 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`[^stdlib] which eventually became the `dataclasses` module in Python 3.7. | |
| [^stdlib]: The highly readable PEP also explains why *attrs* wasn't just added to the standard library. | |
| Don't believe the myths and rumors. | |
| *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](https://medium.com/@Pilot-EPD-Blog/mypy-and-attrs-e1b0225e9ac6). | |
| And so it happened that *attrs* [shipped](https://www.attrs.org/en/17.3.0.post2/changelog.html) 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 {func}`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](https://github.com/python-attrs/attrs/commit/88aa1c897dfe2ee4aa987e4a56f2ba1344a17238#diff-4fc63db1f2fcb7c6e464ee9a77c3c74e90dd191d1c9ffc3bdd1234d3a6663dc0R48) 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 {mod}`dataclasses`. | |
| Finally we've decided to take the [Go route](https://go.dev/blog/module-compatibility): | |
| 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](https://github.com/python-attrs/attrs/issues/408) and came up with {func}`attr.define`, {func}`attr.field`, and friends. | |
| Then in January 2019, we [started looking for inconvenient defaults](https://github.com/python-attrs/attrs/issues/487) 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 | |
| ``` |