## 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                            Traceback (most recent call last)
<ipython-input-6-468e53fb51e0> in <module>
----> 1 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                            Traceback (most recent call last)
<ipython-input-7-5213b9df3654> in <module>
----> 1 a1.__slots__ = ['var2']

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

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-8-47cd4105f78c> in <module>
----> 1 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                            Traceback (most recent call last)
<ipython-input-9-79636e85e82f> in <module>
----> 1 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'


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                            Traceback (most recent call last)
<ipython-input-33-032b9cd26579> in <module>
----> 1 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.