attrs/docs/names.md

122 lines
7.5 KiB
Markdown
Raw Normal View History

2022-12-20 06:06:32 +00:00
# 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 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 couldnt 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.dataclasses` 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
```