Slots seem to be poorly documented. What they do is simple, but whether they are used is tricky. This is a little mini-post on slots.
Immutable list of instance attributes
Slots replace the mutable dictionary of instance level attributes (__dict__
)
that most subclasses have with an immutable one. For example:
class NoSlots(object):
pass
class Slots(object):
__slots__ = ("x",)
pass
We need an instance, as class level attributes (shared by all instances of a
class) are not affected by __slots__
.
noslots = NoSlots()
slots = Slots()
noslots.x = 2
slots.x = 2
noslots.y = 3
slots.y = 3 # Fails
AttributeError Traceback (most recent call last)
in () —-> 1 slots.y = 3 # Fails
AttributeError: ‘Slots’ object has no attribute ‘y’
AttributeError Traceback (most recent call last)
AttributeError: ‘Slots’ object has no attribute ‘y’
Since there is no __dict__
to store the instance variable .y
in, this is
error. This makes __slots__
a lot like the list of member variables in other
languages, and lets you see all the possible instance variables in one place,
instead of checking all the methods.
This feature was actually intended, however, to save memory and performance, since each instance now has a specific set of slots they can put variables in, instead of each one tracking the possible variables. However, it doesn’t make much of a difference unless you have a very large number of instances. Python 3.3 reduced the difference even further with key sharing dictionaries. I would argue the previous reason is more important; I’ve caught bugs with misnamed variables when adding slots.
When slots count
Slots only work in a special case: all parent classes have to have __slots__
too (that is, none of them can have an instance __dict__
). Most or all the
builtins do this, so you just need to be careful when subclassing your own or
third party library types.
__slots__
combine in an odd way, but it makes sense: A class has it’s own and
all parent classes __slots__
combined. So, for example, look at the following
subclass:
class Vector2D(object):
__slots__ = ("x", "y")
class Vector3D(Vector2D):
__slots__ = ("z",)
Here, Vector3D
has three instance variables, x, y, and z; since it inherits
the Vector2D slots. You can’t remove slots (as that would break the class
anyway), and you keep the same slots by setting slots to an empty tuple (or
similar). If you ignore slots, the class becomes a __dict__
class.
class Also2Slots(Vector2D):
__slots__ = ()
class NoSlots(Vector2D):
pass
Also2Slots().z = 2 # Won't work, slots are x and y
AttributeError Traceback (most recent call last)
in () —-> 1 Also2Slots().z = 2 # Won’t
work, slots are x and y
AttributeError: ‘Also2Slots’ object has no attribute ‘z’
NoSlots().z = 2 # No error!
Interesting consequence
AttributeError Traceback (most recent call last)
AttributeError: ‘Also2Slots’ object has no attribute ‘z’
NoSlots().z = 2 # No error!
Have you ever wondered why this doesn’t work:
thing = object()
thing.x = 2 # Error!
But this does:
class SubObject(object):
pass
thing = SubObject()
thing.x = 2 # Works!
Now you know, object
has __slots__
, but SubObject
doesn’t.
Side note: In Python 3.3+, the
SimpleNamespace
object is a lot likeobject
without the__slots__
(and a fancy constructor).