| # 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. |
| |
| - You can't call a bare `super()` in a decorated method like `contextlib.contextmanager`. |
| Use `super(YourClass, self)` until we get a way to work around this from Python. |
| See also [#1038](https://github.com/python-attrs/attrs/issues/1038) for more details. |
| |
| |
| 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. |