|
| 1 | +# Glossary |
| 2 | + |
| 3 | +:::{glossary} |
| 4 | +dunder methods |
| 5 | + "Dunder" is a contraction of "double underscore". |
| 6 | + |
| 7 | + It's methods like `__init__` or `__eq__` that are sometimes also called *magic methods* or it's said that they implement an *object protocol*. |
| 8 | + |
| 9 | + In spoken form, you'd call `__init__` just "dunder init". |
| 10 | + |
| 11 | + 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. |
| 12 | + |
| 13 | +dict classes |
| 14 | + A regular class whose attributes are stored in the {attr}`object.__dict__` attribute of every single instance. |
| 15 | + 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. |
| 16 | + |
| 17 | + 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)). |
| 18 | + |
| 19 | +slotted classes |
| 20 | + 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. |
| 21 | + 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)). |
| 22 | + |
| 23 | + Their main advantage is that they use less memory on CPython[^pypy] and are slightly faster. |
| 24 | + |
| 25 | + However, they also come with several possibly surprising gotchas: |
| 26 | + |
| 27 | + - Slotted classes don't allow for any other attribute to be set except for those defined in one of the class' hierarchies `__slots__`: |
| 28 | + |
| 29 | + ```{eval-rst} |
| 30 | + .. doctest:: |
| 31 | +
|
| 32 | + >>> from attr import define |
| 33 | + >>> @define |
| 34 | + ... class Coordinates: |
| 35 | + ... x: int |
| 36 | + ... y: int |
| 37 | + ... |
| 38 | + >>> c = Coordinates(x=1, y=2) |
| 39 | + >>> c.z = 3 |
| 40 | + Traceback (most recent call last): |
| 41 | + ... |
| 42 | + AttributeError: 'Coordinates' object has no attribute 'z' |
| 43 | + ``` |
| 44 | +
|
| 45 | + - 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. |
| 46 | + If you must inherit from other classes, try to inherit only from other slotted classes. |
| 47 | +
|
| 48 | + - However, [it's not possible](https://docs.python.org/3/reference/datamodel.html#notes-on-using-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`). |
| 49 | +
|
| 50 | + - It's not possible to monkeypatch methods on slotted classes. |
| 51 | + This can feel limiting in test code, however the need to monkeypatch your own classes is usually a design smell. |
| 52 | +
|
| 53 | + 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: |
| 54 | +
|
| 55 | + ```{doctest} |
| 56 | + >>> import unittest.mock |
| 57 | + >>> @define |
| 58 | + ... class Slotted: |
| 59 | + ... x: int |
| 60 | + ... |
| 61 | + ... def method(self): |
| 62 | + ... return self.x |
| 63 | + >>> s = Slotted(42) |
| 64 | + >>> s.method() |
| 65 | + 42 |
| 66 | + >>> with unittest.mock.patch.object(s, "method", return_value=23): |
| 67 | + ... pass |
| 68 | + Traceback (most recent call last): |
| 69 | + ... |
| 70 | + AttributeError: 'Slotted' object attribute 'method' is read-only |
| 71 | + >>> @define(slots=False) |
| 72 | + ... class Dicted(Slotted): |
| 73 | + ... pass |
| 74 | + >>> d = Dicted(42) |
| 75 | + >>> d.method() |
| 76 | + 42 |
| 77 | + >>> with unittest.mock.patch.object(d, "method", return_value=23): |
| 78 | + ... assert 23 == d.method() |
| 79 | + ``` |
| 80 | +
|
| 81 | + - Slotted classes must implement {meth}`__getstate__ <object.__getstate__>` and {meth}`__setstate__ <object.__setstate__>` to be serializable with {mod}`pickle` protocol 0 and 1. |
| 82 | + Therefore, *attrs* creates these methods automatically for slotted classes. |
| 83 | +
|
| 84 | + :::{note} |
| 85 | + 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. |
| 86 | +
|
| 87 | + This can be avoided by setting `@attr.s(getstate_setstate=False)` or by setting `@attr.s(auto_detect=True)`. |
| 88 | +
|
| 89 | + {func}`~attrs.define` sets `auto_detect=True` by default. |
| 90 | + ::: |
| 91 | +
|
| 92 | + Also, [think twice](https://www.youtube.com/watch?v=7KnfGDajDQw) before using {mod}`pickle`. |
| 93 | +
|
| 94 | + - Slotted classes are weak-referenceable by default. |
| 95 | + This can be disabled in CPython by passing `weakref_slot=False` to `@attr.s` [^pypyweakref]. |
| 96 | +
|
| 97 | + - 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. |
| 98 | + While it tries to do that as graciously as possible, certain metaclass features like {meth}`object.__init_subclass__` do not work with slotted classes. |
| 99 | +
|
| 100 | + - The {attr}`class.__subclasses__` attribute needs a garbage collection run (which can be manually triggered using {func}`gc.collect`), for the original class to be removed. |
| 101 | + See issue [#407](https://github.com/python-attrs/attrs/issues/407) for more details. |
| 102 | +::: |
| 103 | +
|
| 104 | +[^pypy]: On PyPy, there is no memory advantage in using slotted classes. |
| 105 | +
|
| 106 | +[^pypyweakref]: On PyPy, slotted classes are naturally weak-referenceable so `weakref_slot=False` has no effect. |
0 commit comments