Python Native slots Attribute Exploration

python
technical
exploration
Diving into Python to see what slots does and how it can be used
Author

Kevin Bird

Published

May 21, 2021

Introduction

While reading the python data model documentation, I came across something I hadn’t seen before. __slots__ is an optional argument that allows users to “explicitly declare data members”. It is an interesting concept that I haven’t seen utilized, but perhaps the reason is that not many people are aware it exists. I am going to explore this attribute that is available to see if it might provide value for my future projects. According to this blog post, __slots__ can significantly reduce the amount of ram required to create objects (40-50%!). Now let’s dive in and figure out how it’s used!

Python Version Check:
3.8.8 (default, Apr 13 2021, 19:58:26) 
[GCC 7.3.0]

Example #1: Typical Case

The first example we look at is the working example. we will have a class A1 with slots set to accept one var named var1.

class A1:
    __slots__ = ['var1']
    def __init__(self, value_passed_through_here):
        self.var1 = value_passed_through_here
a1 = A1(1); a1.var1
1

a has been created and everything is going well. Let’s try adding another attribute.

a1.var2 = "but can I set another var?"
AttributeError: 'A1' object has no attribute 'var2'

a.var2 fails as expected because it isn’t in the __slots__ list and __slots__ is read-only so it cannot be updated.

a1.__slots__ = ['var2']
AttributeError: 'A1' object attribute '__slots__' is read-only
a1.__slots__ = ['var1', 'var2']
AttributeError: 'A1' object attribute '__slots__' is read-only

When __slots__ is used, the __dict__ value is not set. Let’s explore that a little further though.

a1.__dict__
AttributeError: 'A1' object has no attribute '__dict__'

Example #2a: Exploring __dict__ Without Using __slots__

class A2A:
    def __init__(self, value_passed_through_here):
        self.var1 = value_passed_through_here
a2a = A2A(1)

var1 shows up as expected when creating an object

a2a.__dict__
{'var1': 1}
a2a.var2 = 'adding a second thing'

Adding a second variable adds it to the __dict__ as expected

a2a.__dict__
{'var1': 1, 'var2': 'adding a second thing'}

Example #2b: Exploring __dict__ When Using __slots__

class A2B:
    __slots__ = ['var1', '__dict__']
    def __init__(self, value_passed_through_here):
        self.var1 = value_passed_through_here
a2b = A2B(1)
a2b.__dict__
{}

__dict__ exists now since we added it to __slots__, but it isn’t populating the __dict__ like normal. We are still able to call the attribute var1 though.

a2b.var1
1
a2b.var2 = "test if we can add new variables now"

Surprisingly, once we add __dict__ to the __slots__ list, adding a new var works.

a2b.__dict__
{'var2': 'test if we can add new variables now'}

When we look at __dict__ after adding var2, there is an entry in __dict__ as well.

a2b.var2
'test if we can add new variables now'

So if we enable __dict__ we are able to add new items to the __dict__, but __dict__ has to be explicitly defined to work.

Example 3: Inheritance

Now that we’ve explored __slots__, let’s see how it behaves when one class is inherited from another.

class A3:
    __slots__ = ['a']
    def __init__(self, a):
        self.a = a
a3 = A3(1); a3.a
1
class B3A(A3):
    def __init__(self):
        self.a = 1
        self.b = 2
b3a = B3A()
b3a.__slots__
['a']
b3a.__dict__
{'b': 2}
b3a.a
1
b3a.c = "can I set c?"
b3a.__dict__
{'b': 2, 'c': 'can I set c?'}

So when B3A is inherited from A3, it uses the slots class, but it also reverts back in a lot of ways to a normal, non-slots, class again. The last thing I’m going to try is actually setting a __slots__ in B3B just to see what happens

class B3B(A3):
    __slots__ = ['a','b']
    def __init__(self):
        self.a = 1
        self.b = 2
b3b = B3B()
b3b.c = "can I set this?"
AttributeError: 'B3B' object has no attribute 'c'

So now that we have given B3B a __slots__ it is no longer behaves the same way that B3A is.

Here is what the official documentation says about this: >The action of a __slots__ declaration is not limited to the class where it is defined. __slots__ declared in parents are available in child classes. However, child subclasses will get a __dict__ and __weakref__ unless they also define __slots__ (which should only contain names of any additional slots). (https://docs.python.org/3.8/reference/datamodel.html#notes-on-using-slots 5th bullet)

Conclusion

__slots__ is an interesting concept that is built into Python that I hadn’t heard of and wanted to explore. Hopefully this notebook is informative to other Python users as well. Things didn’t always behave as I would have expected and that’s part of the fun of actually testing out the code to see how things work in practice.