Python objects are significantly heavier than C/C++ structures due to dynamic typing and memory overhead. For memory-intensive applications, standard Python containers can cause performance bottlenecks or OOM (Out of Memory) errors.
Let's look at some ways in which we can use this memory effectively and reduce the size of objects.
Using built-in Dictionaries (High Overhead)
Dictionaries prioritize lookup speed (hashing) over memory efficiency. A standard dictionary incurs significant overhead for the hash table structure.
import sys
# Standard dict
Coordinates = {'x': 3, 'y': 0, 'z': 1}
print(f"Size: {sys.getsizeof(Coordinates)} bytes")
Output
Size: 184 bytes
Notice that one instance of the data type dictionary takes 184 bytes, which makes it inefficient for storing millions of object instances.
Tuples (Immutable Efficiency)
Tuples store data in fixed-size arrays without hash table overhead. They are the most efficient built-in structure for immutable data.
import sys
# Tuple (x, y, z)
Coordinates = (3, 0, 1)
print(f"Size: {sys.getsizeof(Coordinates)} bytes")
Output
Size: 64 bytes
It provides excellent memory footprint, but lacks mutability and readability (indices vs. attribute names).
Classes with __slots__ (Optimal)
Standard Python classes store attributes in a private dictionary (__dict__). This creates a "double overhead": the object instance plus the internal dictionary. To overcome this, we can use "define __slots__". This instructs Python to allocate a fixed amount of memory for specific attributes, preventing the creation of __dict__.
import sys
class PointSlots:
# Suppresses __dict__ generation
__slots__ = ['x', 'y', 'z']
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
obj = PointSlots(3, 0, 1)
print(f"Size: {sys.getsizeof(obj)} bytes")
Output
Size: 56 bytes
It retains class usability (attribute names) with tuple-like memory efficiency.
Note: "sys.getsizeof()" function is non-recursive (shallow). It measures the container size, not referenced objects. However, replacing a Dictionary container (~232 bytes) with a Slotted container (48 bytes) yields massive savings at scale.
DataClasses (Python 3.10+)
Python 3.10 introduced the slots=True parameter to the dataclasses module. This automates the __slots__ creation, removing boilerplate.
import sys
from dataclasses import dataclass
@dataclass(slots=True)
class PointData:
x: int
y: int
z: int
obj = PointData(3, 0, 1)
print(f"Size: {sys.getsizeof(obj)} bytes")
Output
Size: 56 bytes
Third-Party Libraries (recordclass)
For scenarios requiring mutable, tuple-like objects without writing classes, recordclass (C-extension) is a viable alternative. It's an external library so it needs to be installed first.
Install the "recordclass" library using the following command in terminal:
pip install recordclass
from recordclass import recordclass
import sys
Point = recordclass('Point', ('x', 'y', 'z'))
obj = Point(3, 0, 1)
print(f"Size: {sys.getsizeof(obj)} bytes")
Output
Size: 48 bytes
Summary of Memory Consumption
| Structure | Approx Size (Container) | Mutable? | Efficiency |
|---|---|---|---|
| Dictionary | ~232 bytes | Yes | Low |
| Standard Class | ~280 bytes (obj + __dict__) | Yes | Very Low |
| Tuple | 48 bytes | No | High |
| Class (__slots__) | 48 bytes | Yes | High |
| Dataclass (slots=True) | 48 bytes | Yes | High |