- 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.
- slotted classes¶
A class whose instances have no
object.__dict__attribute and define their attributes in a
object.__slots__attribute instead. In
attrs, they are created by passing
@attr.s(and are on by default in
Their main advantage is that they use less memory on CPython 1 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
>>> 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 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:
>>> 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
__setstate__to be serializable with
pickleprotocol 0 and 1. Therefore,
attrscreates these methods automatically for
slots=Trueclasses (Python 2 uses protocol 0 by default).
This can be avoided by setting
@attr.s(getstate_setstate=False)or by setting
Slotted classes are weak-referenceable by default. This can be disabled in CPython by passing
Since it’s currently impossible to make a class slotted after it’s been created,
attrshas 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.