AW Dev Rethought

⚖️ There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies - C.A.R. Hoare

🧠 Python DeepCuts — 💡 The Power of __slots__ Revisited


Description:

slots is often introduced as a memory optimization trick. But beneath the surface, it fundamentally changes Python’s object memory layout and attribute access model.

This DeepCut revisits slots from an internal perspective:

  • how attributes are stored by default
  • what actually disappears when slots are enabled
  • why attribute lookup becomes more efficient
  • when slots are helpful — and when they hurt

🧩 Default Attribute Storage: dict

By default, every Python object stores its attributes in a per-instance dictionary.

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

n = Normal(10, 20)
n.__dict__

Each instance carries:

  • a pointer to a dictionary
  • a hash table for keys and values
  • dynamic growth capability

This is flexible — but memory-heavy.


🧠 What slots Changes Internally?

When slots is defined:

  • Python removes the per-instance dict
  • Attribute names are fixed at class creation
  • Storage becomes a compact, array-like structure
class Slotted:
    __slots__ = ("x", "y")

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

Instances no longer carry a dictionary — attributes are stored by offset, not by name lookup.


🔍 Memory Footprint: Dict vs Slots:

The real memory savings come from eliminating dict.

import sys

normal = Normal(1, 2)
slotted = Slotted(1, 2)

sys.getsizeof(normal)
sys.getsizeof(slotted)

While the object size difference may appear small, the missing dictionary scales massively when creating thousands or millions of objects.

This is why slots matter in:

  • ORMs
  • simulations
  • AST nodes
  • compiler internals
  • ML feature objects

🧱 Attribute Lookup Paths:

Attribute access differs internally:

  • Normal objects → obj.dict['x']
  • Slotted objects → fixed memory offset
normal.__dict__["x"]
slotted.x

Slots reduce:

  • hash lookups
  • pointer indirections
  • cache misses

Result: slightly faster attribute access with much lower memory overhead.


⚠️ Slots Restrict Dynamic Attributes:

Slots intentionally prevent adding new attributes at runtime.

try:
    slotted.z = 30
except AttributeError as e:
    print(e)

This is a feature — not a bug — enforcing a fixed object schema.

However, it makes slots unsuitable for:

  • monkey-patching
  • dynamic data models
  • evolving schemas

🧬 Inheritance and slots:

Slots don’t automatically merge across inheritance hierarchies.

class Base:
    __slots__ = ("a",)

class Child(Base):
    __slots__ = ("b",)

c = Child()
c.a = 1
c.b = 2

Each class defines its own slot layout.

Mismanaging this can lead to subtle bugs or missing attributes.


🔄 Hybrid Slots: Allowing dict Explicitly:

You can opt back into dynamic attributes by including dict.

class Hybrid:
    __slots__ = ("x", "__dict__")

h = Hybrid()
h.x = 10
h.y = 20

🧠 When slots Is Worth Using?

Use slots when:

  • You create many instances
  • Attribute names are fixed
  • Memory pressure matters
  • Performance predictability is important

Avoid slots when:

  • You rely on dynamic attributes
  • You expect frequent schema changes
  • You need heavy runtime introspection

✅ Key Points:

  • Default objects store attributes in dict
  • slots removes the per-instance dictionary
  • Attributes become fixed offsets in memory
  • Lookup becomes faster and more cache-friendly
  • Slots enforce structure and reduce memory usage
  • Inheritance and flexibility must be handled carefully

slots is not a micro-optimization — it’s a structural design choice.

Code Snippet:

import sys

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

class Slotted:
    __slots__ = ("x", "y")

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

n = Normal(10, 20)
s = Slotted(10, 20)

print("Normal __dict__:", n.__dict__)
print("Slotted has __dict__:", hasattr(s, "__dict__"))

print("Normal size:", sys.getsizeof(n))
print("Slotted size:", sys.getsizeof(s))

print("Normal x via dict:", n.__dict__["x"])
print("Slotted x direct:", s.x)

try:
    s.z = 30
except AttributeError as e:
    print("Slot restriction:", e)

class Base:
    __slots__ = ("a",)

class Child(Base):
    __slots__ = ("b",)

c = Child()
c.a = 1
c.b = 2
print("Inherited slots:", c.a, c.b)

class Hybrid:
    __slots__ = ("x", "__dict__")

h = Hybrid()
h.x = 10
h.y = 20
print("Hybrid attrs:", h.__dict__)

Link copied!

Comments

Add Your Comment

Comment Added!