Python Native __slots__ Attribute Exploration
Diving into Python to see what __slots__ does and how it can be used
- Introduction
- Example #1: Typical Case
- Example #2a: Exploring __dict__ Without Using __slots__
- Example #2b: Exploring __dict__ When Using __slots__
- Example 3: Inheritance
- Conclusion
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!
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
a
has been created and everything is going well. Let's try adding another attribute.
a1.var2 = "but can I set another var?"
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']
a1.__slots__ = ['var1', 'var2']
When __slots__ is used, the __dict__ value is not set. Let's explore that a little further though.
a1.__dict__
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__
a2a.var2 = 'adding a second thing'
Adding a second variable adds it to the __dict__ as expected
a2a.__dict__
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
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__
When we look at __dict__ after adding var2, there is an entry in __dict__ as well.
a2b.var2
So if we enable __dict__ we are able to add new items to the __dict__, but __dict__ has to be explicitly defined to work.
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
class B3A(A3):
def __init__(self):
self.a = 1
self.b = 2
b3a = B3A()
b3a.__slots__
b3a.__dict__
b3a.a
b3a.c = "can I set c?"
b3a.__dict__
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?"
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)
__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.