ExampleEdit & Run

how to use __slots__ 

class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(3, 4)
print(f'x: {p.x}')
print(f'y: {p.y}')
copy
Output:
x: 3 y: 4 [Finished in 0.010560058057308197s]

__slots__ is a special class variable in Python that restricts the attributes that can be assigned to an instance of a class.
__slots__ is an iterable(usually a tuple) that stores the names of the allowed attributes for a class. It specifies the list of attribute names that can be assigned to instances of the class.

If __slots__ is defined, an exception will be raised if  an attribute that is not in the list is assigned to an instance.

ExampleEdit & Run
class Point:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.z = 5 #Raises an error because 'z' is not in __slots__

p = Point(2, 3)
copy
Output:
Traceback (most recent call last):   File "<string>", line 8, in <module>   File "<string>", line 6, in __init__ AttributeError: 'Point' object has no attribute 'z' [Finished in 0.010049204807728529s]

In the above example, an AttributeError  exception is raised, because we tried to assign attribute z to an instance when "z" is not present in the __slots__ list. 

We also cannot add attributes to an instance dynamically.

ExampleEdit & Run
class Point:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(2, 3)
p.z = 5 #Raises an exception
copy
Output:
Traceback (most recent call last):   File "<string>", line 8, in <module> AttributeError: 'Point' object has no attribute 'z' [Finished in 0.010171724017709494s]

Why is __slots__ important?

By default, class instances have a dictionary called __dict__ that stores all the attributes, and their associated values. 

ExampleEdit & Run

the __dict__ variable 

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(3, 4)
print("__dict__: ", p.__dict__)
copy
Output:
__dict__:  {'x': 3, 'y': 4} [Finished in 0.009812699165195227s]

Using the dictionary, __dict__,  allows for dynamic creation of attributes at runtime, which can be convenient but can also lead to potential memory inefficiency.

The __slots__  variable allows for more efficient memory usage as it limits the attributes that can be assigned to an instance to only those listed in __slots__ declaration.

Using  __slots__ can offer considerable performance benefits because it eliminates the need for a dictionary to store attributes. When __slots__ is declared, the  attributes are stored in a fixed-size array on the object itself. This makes accessing and setting attributes much faster as the interpreter does not have to perform dictionary lookups.

ExampleEdit & Run
class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(3, 4)
print("__slots__: ", p.__slots__)
print("__dict__: ", p.__dict__) #The __dict__ is not used when __slots__ is declared.
copy
Output:
__slots__:  ('x', 'y') Traceback (most recent call last):   File "<string>", line 9, in <module> AttributeError: 'Point' object has no attribute '__dict__'. Did you mean: '__dir__'? [Finished in 0.010258453898131847s]

__slots__ is particularly useful when we know exactly what attributes class instances should have, and we don't want to assign arbitrary attributes dynamically at runtime.

__slots__ with inheritance

When a class defines __slots__, it only applies to instances of that class and does not automatically apply to its child classes. A child class does not inherit the parent class's __slots__.

By default, a child class will still use the __dict__  dictionary for storing the attribute, in addition to parents __slots__, unless it also defines its own __slots__.

ExampleEdit & Run
class Point:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.z = 10 #Raises an error because 'z' is not in __slots__

#inherits from Point
class Point3d(Point):
     def __init__(self, x, y, z):
         super().__init__(x, y)
         self.z = z #does not raise an error

p = Point3d(2, 3, 4)
print('__slots: ', p.__slots__) #parents __slots__
print("__dict__: ", p.__dict__) #child's __dict__

print("x: ", p.x)
print("y: ", p.y)
print("z: ", p.z)
copy
Output:
__slots:  ('x', 'y') __dict__:  {'z': 4} x:  2 y:  3 z:  4 [Finished in 0.010323648806661367s]

A child class can also declare its own __slots__, without including those declared by its parent classes.

ExampleEdit & Run
class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.z = 10 #Raises an error because 'z' is not in __slots__

#inherits from Point
class Point3d(Point):
     __slots__ = ("z", )
     def __init__(self, x, y, z):
         super().__init__(x, y)
         self.z = z #does not raise an error

p = Point3d(2, 3, 4)

print("x: ", p.x)
print("y: ", p.y)
print("z: ", p.z)
copy
Output:
x:  2 y:  3 z:  4 [Finished in 0.009497730992734432s]