Slots in Python

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’

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

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 like object without the __slots__ (and a fancy constructor).

programming  python 
comments powered by Disqus