Python’s __slots__ feature can dramatically reduce the memory footprint of your objects, but it doesn’t work by simply declaring them; it fundamentally changes how Python manages an object’s instance attributes.

Let’s see this in action with a simple class.

import sys

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

class SlottedPoint:
    __slots__ = ['x', 'y']

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

# Create instances
p1 = Point(1, 2)
p2 = SlottedPoint(1, 2)

# Compare memory usage
print(f"Memory for Point object: {sys.getsizeof(p1)} bytes")
print(f"Memory for SlottedPoint object: {sys.getsizeof(p2)} bytes")

When you run this, you’ll likely see a significant difference, with SlottedPoint consuming much less memory. The exact numbers will vary based on your Python version and system, but the trend is consistent.

So, what’s happening under the hood? By default, every Python object has a __dict__ attribute, which is a dictionary storing the object’s instance variables. This is incredibly flexible – you can add or remove attributes on the fly. However, this flexibility comes at a memory cost. Each __dict__ has its own overhead for managing keys, hash tables, and dynamic resizing.

When you define __slots__ in a class, you’re telling Python, "I know exactly which attributes this object will have, and I don’t need a dynamic __dict__." Instead of a flexible dictionary, Python allocates a fixed-size structure for these specific attributes. Think of it like replacing a messy drawer where you can shove anything with a well-organized set of labeled compartments.

The primary problem __slots__ solves is the memory overhead associated with the __dict__ for every instance. If you have millions of small objects, like points on a graph, sensor readings, or database records, this overhead can accumulate rapidly, leading to higher memory consumption and potentially slower performance due to increased cache pressure.

Here’s how to use it effectively:

  1. Declare __slots__: Define __slots__ as a tuple or list of strings, where each string is the name of an instance attribute you intend to use.

    class Vector:
        __slots__ = ('x', 'y', 'z') # Tuple is common
    
        def __init__(self, x, y, z):
            self.x = x
            self.y = y
            self.z = z
    
  2. No __dict__ by default: Instances of Vector will not have a __dict__. Attempting to access v.__dict__ for an instance v will raise an AttributeError.

  3. No dynamic attribute assignment: You cannot add new attributes to an instance that are not listed in __slots__.

    v = Vector(1, 2, 3)
    # v.w = 4 # This will raise an AttributeError
    
  4. Inheritance: If a subclass also defines __slots__, its slots will be added to the parent’s. If a subclass does not define __slots__, it will automatically get a __dict__, and its instances will inherit the slots from the parent class plus have their own __dict__. To maintain memory savings in subclasses, you must also declare __slots__.

    class ColoredVector(Vector):
        __slots__ = ('color',) # Inherits x, y, z from Vector
    
        def __init__(self, x, y, z, color):
            super().__init__(x, y, z)
            self.color = color
    

    If you wanted to avoid __dict__ in ColoredVector, you’d need to declare __slots__ = ('x', 'y', 'z', 'color') or __slots__ = ('color',) and rely on the parent’s slots being inherited. The latter is more common and idiomatic.

The key advantage is memory efficiency. For classes with many instances and a fixed set of attributes, __slots__ can reduce memory usage by 50-75% or even more, depending on the attribute types and the Python version. This is because instead of a dictionary per instance, Python uses a more compact, C-level array for storing the attribute values directly.

A common pitfall is forgetting that __slots__ prevents the creation of __dict__. This means you lose the ability to dynamically add attributes to instances, which can be problematic if you rely on that feature. Another subtlety is how __slots__ interacts with inheritance. If a base class has __slots__ and a subclass does not, the subclass instances will have a __dict__ in addition to the slots inherited from the base. To preserve memory savings, all classes in an inheritance hierarchy that use __slots__ must declare them.

The next step when optimizing object memory is often understanding how __slots__ affects object initialization and attribute access performance, and when it’s truly beneficial versus when it adds complexity without significant gains.

Want structured learning?

Take the full Python course →