attrs/docs/glossary.md

127 lines
6.3 KiB
Markdown

# Glossary
:::{glossary}
dunder methods
"Dunder" is a contraction of "double underscore".
It's methods like `__init__` or `__eq__` that are sometimes also called *magic methods* or it's said that they implement an *object protocol*.
In spoken form, you'd call `__init__` just "dunder init".
Its first documented use is a [mailing list posting](https://mail.python.org/pipermail/python-list/2002-September/155836.html) by Mark Jackson from 2002.
dict classes
A regular class whose attributes are stored in the {attr}`object.__dict__` attribute of every single instance.
This is quite wasteful especially for objects with very few data attributes and the space consumption can become significant when creating large numbers of instances.
This is the type of class you get by default both with and without *attrs* (except with the next APIs {func}`attrs.define()`, [`attrs.mutable()`](attrs.mutable), and [`attrs.frozen()`](attrs.frozen)).
slotted classes
A class whose instances have no {attr}`object.__dict__` attribute and [define](https://docs.python.org/3/reference/datamodel.html#slots) their attributes in a `object.__slots__` attribute instead.
In *attrs*, they are created by passing `slots=True` to `@attr.s` (and are on by default in {func}`attrs.define()`, [`attrs.mutable()`](attrs.mutable), and [`attrs.frozen()`](attrs.frozen)).
Their main advantage is that they use less memory on CPython[^pypy] and are slightly faster.
However, they also come with several possibly surprising gotchas:
- Slotted classes don't allow for any other attribute to be set except for those defined in one of the class' hierarchies `__slots__`:
```{doctest}
>>> from attr import define
>>> @define
... class Coordinates:
... x: int
... y: int
...
>>> c = Coordinates(x=1, y=2)
>>> c.z = 3
Traceback (most recent call last):
...
AttributeError: 'Coordinates' object has no attribute 'z'
```
- Slotted classes can inherit from other classes just like non-slotted classes, but some of the benefits of slotted classes are lost if you do that.
If you must inherit from other classes, try to inherit only from other slotted classes.
- However, [it's not possible](https://docs.python.org/3/reference/datamodel.html#slots) to inherit from more than one class that has attributes in `__slots__` (you will get an `TypeError: multiple bases have instance lay-out conflict`).
- It's not possible to monkeypatch methods on slotted classes.
This can feel limiting in test code, however the need to monkeypatch your own classes is usually a design smell.
If you really need to monkeypatch an instance in your tests, but don't want to give up on the advantages of slotted classes in production code, you can always subclass a slotted class as a dict class with no further changes and all the limitations go away:
```{doctest}
>>> import unittest.mock
>>> @define
... class Slotted:
... x: int
...
... def method(self):
... return self.x
>>> s = Slotted(42)
>>> s.method()
42
>>> with unittest.mock.patch.object(s, "method", return_value=23):
... pass
Traceback (most recent call last):
...
AttributeError: 'Slotted' object attribute 'method' is read-only
>>> @define(slots=False)
... class Dicted(Slotted):
... pass
>>> d = Dicted(42)
>>> d.method()
42
>>> with unittest.mock.patch.object(d, "method", return_value=23):
... assert 23 == d.method()
```
- Slotted classes must implement {meth}`__getstate__ <object.__getstate__>` and {meth}`__setstate__ <object.__setstate__>` to be serializable with {mod}`pickle` protocol 0 and 1.
Therefore, *attrs* creates these methods automatically for slotted classes.
:::{note}
When decorating with `@attr.s(slots=True)` and the class already implements the {meth}`__getstate__ <object.__getstate__>` and {meth}`__setstate__ <object.__setstate__>` methods, they will be *overwritten* by *attrs* autogenerated implementation by default.
This can be avoided by setting `@attr.s(getstate_setstate=False)` or by setting `@attr.s(auto_detect=True)`.
{func}`~attrs.define` sets `auto_detect=True` by default.
:::
Also, [think twice](https://www.youtube.com/watch?v=7KnfGDajDQw) before using {mod}`pickle`.
- Slotted classes are weak-referenceable by default.
This can be disabled in CPython by passing `weakref_slot=False` to `@attr.s` [^pypyweakref].
- Since it's currently impossible to make a class slotted after it's been created, *attrs* has to replace your class with a new one.
While it tries to do that as graciously as possible, certain metaclass features like {meth}`object.__init_subclass__` do not work with slotted classes.
- The {attr}`type.__subclasses__` attribute needs a garbage collection run (which can be manually triggered using {func}`gc.collect`), for the original class to be removed.
See issue [#407](https://github.com/python-attrs/attrs/issues/407) for more details.
- Pickling of slotted classes will fail if you define a class with missing attributes.
This situation can occur if you define an `attrs.field(init=False)` and don't set the attribute by hand before pickling.
field
As the project name suggests, *attrs* is all about attributes.
We especially tried to emphasize that we only care about attributes and not about the classes themselves -- because we believe the class belongs to the user.
This explains why the traditional API uses an {func}`attr.ib` (or ``attrib``) function to define attributes and we still use the term throughout the documentation.
However, with the emergence of {mod}`dataclasses`, [Pydantic](https://docs.pydantic.dev/latest/concepts/fields/), and other libraries, the term "field" has become a common term for a predefined attribute on a class in the Python ecosystem.
So with out new APIs, we've embraced it too by calling the function to create them {func}`attrs.field`, and use the term "field" throughout the documentation interchangeably.
See also {doc}`names`.
attribute
See {term}`field`.
:::
[^pypy]: On PyPy, there is no memory advantage in using slotted classes.
[^pypyweakref]: On PyPy, slotted classes are naturally weak-referenceable so `weakref_slot=False` has no effect.