From 82b9e92f25375e252a0c4dcc9daaaa5de885ad47 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Fri, 21 Aug 2020 08:21:23 +0200 Subject: [PATCH] Explain slots=True and monkeypatching ref #668 Signed-off-by: Hynek Schlawack --- docs/glossary.rst | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 72964f351..0d6b75ca0 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -11,10 +11,10 @@ Glossary slotted classes A class whose instances have no ``__dict__`` attribute and `define `_ their attributes in a ``__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`. + 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]_. + 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: @@ -39,6 +39,37 @@ Glossary - However, `it's not possible `_ 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__ ` and :meth:`__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).