127 lines
6.3 KiB
Markdown
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 our 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.
|