96 lines
5.0 KiB
ReStructuredText
96 lines
5.0 KiB
ReStructuredText
Glossary
|
|
========
|
|
|
|
.. glossary::
|
|
|
|
dict classes
|
|
A regular class whose attributes are stored in the `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 `attr.define`, `attr.mutable`, and `attr.frozen`).
|
|
|
|
slotted classes
|
|
A class whose instances have no `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 `attr.define`/`attr.mutable`/`attr.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::
|
|
|
|
>>> import attr
|
|
>>> @attr.s(slots=True)
|
|
... class Coordinates(object):
|
|
... x = attr.ib()
|
|
... y = attr.ib()
|
|
...
|
|
>>> 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#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``).
|
|
|
|
- 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 attr, unittest.mock
|
|
>>> @attr.s(slots=True)
|
|
... class Slotted(object):
|
|
... x = attr.ib()
|
|
...
|
|
... 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
|
|
>>> @attr.s # implies '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 `pickle` protocol 0 and 1.
|
|
Therefore, ``attrs`` creates these methods automatically for ``slots=True`` classes (Python 2 uses protocol 0 by default).
|
|
|
|
.. note::
|
|
|
|
If the ``@attr.s(slots=True)`` decorated 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)``.
|
|
|
|
Also, `think twice <https://www.youtube.com/watch?v=7KnfGDajDQw>`_ before using `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 `object.__init_subclass__` do not work with slotted classes.
|
|
|
|
- The `class.__subclasses__` attribute needs a garbage collection run (which can be manually triggered using `gc.collect`), for the original class to be removed.
|
|
See issue `#407 <https://github.com/python-attrs/attrs/issues/407>`_ for more details.
|
|
|
|
|
|
.. [#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.
|