Deep Dive in Slots Optimizing Python Class Memory Usage
James Reed
Infrastructure Engineer · Leapcell

Deep Dive in Slots Optimizing Python Class Memory Usage
Python, gefeiert für seine Lesbarkeit und Flexibilität, wird manchmal wegen seines Speicher-Footprints kritisiert, insbesondere wenn es um eine große Anzahl von Objekten geht. Jede Instanz einer Standard-Python-Klasse verfügt über ein integriertes Wörterbuch (__dict__
) zur Speicherung ihrer Attribute. Obwohl unglaublich flexibel, kann diese wörterbuchbasierte Speicherung überraschend speicherintensiv sein. Für Anwendungen, die riesige Datenmengen verarbeiten, oder in ressourcenbeschränkten Umgebungen kann dieser Overhead zu einem erheblichen Engpass werden. Hier kommt Pythons __slots__
Attribut als mächtiges Werkzeug zur Optimierung der Speichernutzung ins Spiel und bietet einen Weg zu schlankeren, effizienteren Python-Objekten. Lassen Sie uns eintauchen, wie __slots__
seine Magie wirkt und wie Sie es nutzen können, um speichereffizientere Python-Anwendungen zu erstellen.
Understanding the Core Concepts
Bevor wir __slots__
untersuchen, lassen Sie uns einige grundlegende Konzepte klären, die für das Verständnis seiner Auswirkungen entscheidend sind.
-
__dict__
: Standardmäßig hat jede Instanz einer benutzerdefinierten Klasse in Python ein__dict__
Attribut. Dies ist ein Wörterbuch, das alle instanzspezifischen Attribute speichert. Wenn Sie einem Attribut einen Wert zuweisen (obj.attribute
), wird dieser im Wesentlichen inobj.__dict__['attribute']
gespeichert. Dies bietet unglaubliche Dynamik und ermöglicht es Ihnen, einem Objekt unterwegs neue Attribute hinzuzufügen. Wörterbücher selbst verbrauchen jedoch viel Speicher, und dieser Overhead wird für jede Instanz dupliziert. -
Instance Attributes: Dies sind Variablen, die mit einer bestimmten Instanz einer Klasse verbunden sind und eindeutige Daten für dieses Objekt enthalten. Zum Beispiel wären in einer
Person
-Klassename
undage
Instanzattribute. -
Memory Footprint: Dies bezieht sich auf die Menge an Computerspeicher, die ein Objekt oder Programm verbraucht. Im Kontext von Python-Klassen bedeutet die Reduzierung des Speicher-Footprints, dass jede Objektinstanz weniger RAM verbraucht.
The Mechanism of __slots__
Das __slots__
Attribut teilt Python, wenn es in einer Klasse definiert ist, mit, dass für jede Instanz kein instanzeigenes __dict__
erstellt werden soll. Stattdessen definiert es vorab einen festen Satz von Attributen, die Instanzen dieser Klasse haben werden. Wenn Sie __slots__
definieren, reserviert Python dedizierten Speicher pro Instanz für diese angegebenen Attribute, anstatt ein allgemeines Wörterbuch zu verwenden. Diese direkte Zuweisung reduziert den Speicherverbrauch drastisch, weil:
- Kein
__dict__
Overhead: Der vom__dict__
selbst verbrauchte Speicher (einschließlich Hash-Tabellen-Overhead, Schlüsselspeicher usw.) wird für jede Instanz eliminiert. - Kompaktere Speicherung: Der Zugriff auf Attribute wird schneller, da Python keine Wörterbuch-Suche durchführen muss; es kann direkt auf den festen Offset des Attributs zugreifen.
Lassen Sie uns dies mit einigen Codebeispielen und Speicherinspektionen veranschaulichen.
Without __slots__
:
import sys class Point: def __init__(self, x, y): self.x = x self.y = y p_default = Point(10, 20) print(f"Size of Point instance (default): {sys.getsizeof(p_default)} bytes") # Output will vary but will typically be around 56-64 bytes on a 64-bit system # This size primarily represents the object's internal structure and a pointer to its __dict__. # The actual __dict__ memory is separate. print(f"Size of Point.__dict__ (default): {sys.getsizeof(p_default.__dict__)} bytes") # Output will be significantly larger, e.g., 232 bytes, for the dictionary itself.
Im obigen Beispiel zeigt sys.getsizeof(p_default)
die Größe des Point
-Objekts selbst an, die einen Zeiger auf sein __dict__
enthält. Der eigentliche Speicherfresser ist p_default.__dict__
, ein vollwertiges Wörterbuch zur Speicherung von x
und y
.
With __slots__
:
import sys class SlottedPoint: __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = y p_slotted = SlottedPoint(10, 20) print(f"Size of SlottedPoint instance: {sys.getsizeof(p_slotted)} bytes") # Output will be significantly smaller, e.g., 40 bytes on a 64-bit system. try: print(f"SlottedPoint has __dict__: {p_slotted.__dict__}") except AttributeError as e: print(f"SlottedPoint does not have __dict__: {e}")
Hier meldet sys.getsizeof(p_slotted)
eine viel kleinere Zahl. Der AttributeError
bestätigt, dass p_slotted
kein __dict__
besitzt. Diese Reduzierung des Speichers pro Objekt kann zu erheblichen Einsparungen führen, wenn Sie Hunderte von Tausenden oder Millionen von SlottedPoint
-Objekten haben.
Implications and Considerations
Während __slots__
erhebliche Speichervorteile bietet, bringt es einige Kompromisse mit sich:
- No
__dict__
: Wie gesehen, haben Instanzen von Klassen mit__slots__
kein__dict__
. Das bedeutet, dass Sie nach der Erstellung keine neuen Attribute dynamisch zu einer Instanz hinzufügen können. Alle Attribute müssen in__slots__
vordefiniert sein. - No weak references (by default): Standardmäßig können Objekte mit
__slots__
keine Ziele von schwachen Referenzen sein. Um schwache Referenzen zu ermöglichen, müssen Sie normalerweise'__weakref__'
zu Ihrem__slots__
-Tuple hinzufügen. - Inheritance:
- Wenn eine Unterklasse
__slots__
nicht definiert, hat sie ein__dict__
, auch wenn ihre Elternklasse dies tut. - Wenn eine Unterklasse
__slots__
definiert, sind ihre__slots__
die Kombination ihrer eigenen definierten Slots und der Slots ihrer Eltern. Wenn jedoch ein Elternteil__slots__
definiert hat, muss das Kind sicherstellen, dass es nicht versehentlich__dict__
wieder einführt, es sei denn, dies ist ausdrücklich gewünscht.
- Wenn eine Unterklasse
- Readability: Für sehr einfache Klassen kann
__slots__
die Klassendefinition manchmal weniger flüssig machen, da jedes Attribut explizit aufgeführt werden muss. Für klar definierte Datenstrukturen kann dies jedoch auch die Klarheit verbessern.
When to Use __slots__
__slots__
wird am besten in bestimmten Szenarien eingesetzt:
- Many instances: Wenn Ihre Anwendung eine große Anzahl von Instanzen einer bestimmten Klasse erstellt (z. B. Datenstrukturen, Spielobjekte, ORM-Entitäten).
- Fixed attributes: Wenn die Attribute Ihrer Objekte im Voraus bekannt sind und nach der Erstellung nicht dynamisch geändert werden müssen.
- Memory-constrained environments: In Situationen, in denen die Speichernutzung eine kritische Rolle spielt, wie z. B. in eingebetteten Systemen oder Webservern, die viele gleichzeitige Anfragen bedienen.
Example: Immutable Data Structure
Betrachten Sie ein Szenario, in dem Sie eine CSV-Datei mit Millionen von Zeilen parsen, von denen jede eine Transaktion darstellt.
import sys class Transaction: # Defining slots for memory efficiency __slots__ = ('transaction_id', 'amount', 'currency', 'timestamp') def __init__(self, transaction_id, amount, currency, timestamp): self.transaction_id = transaction_id self.amount = amount self.currency = currency self.timestamp = timestamp # Without slots (for comparison) class TransactionNoSlots: def __init__(self, transaction_id, amount, currency, timestamp): self.transaction_id = transaction_id self.amount = amount self.currency = currency self.timestamp = timestamp # Test memory usage t_slotted = Transaction(1, 100.50, 'USD', '2023-10-27 10:00:00') t_no_slots = TransactionNoSlots(1, 100.50, 'USD', '2023-10-27 10:00:00') print(f"Memory for Slotted Transaction: {sys.getsizeof(t_slotted)} bytes") print(f"Memory for Non-Slotted Transaction: {sys.getsizeof(t_no_slots)} bytes") print(f"Memory for Non-Slotted Transaction __dict__: {sys.getsizeof(t_no_slots.__dict__)} bytes") # If we create 1 million instances: # slotted_objects_memory = 1_000_000 * sys.getsizeof(t_slotted) # non_slotted_objects_memory = 1_000_000 * (sys.getsizeof(t_no_slots) + sys.getsizeof(t_no_slots.__dict__)) # The difference would be in tens or hundreds of MBs.
Der Unterschied in sys.getsizeof
zeigt hier das Potenzial für massive Speichereinsparungen, wenn mit vielen Instanzen von Transaction
gearbeitet wird.
Conclusion
__slots__
ist ein mächtiges, aber oft unterschätztes Feature in Python zur Optimierung der Speichernutzung in der objektorientierten Programmierung. Durch die explizite Deklaration der Attribute, die Ihre Klasseninstanzen besitzen werden, können Sie den Overhead des __dict__
umgehen, was zu erheblich schlankeren Objekten und reduziertem Speicherverbrauch in Ihrer Anwendung führt. Obwohl es die dynamische Flexibilität von Python gegen Speichereffizienz eintauscht, kann das Verständnis seiner Mechanismen und das Wissen, wann es angewendet werden soll, für den Aufbau leistungsstarker, ressourcenbewusster Python-Systeme von unschätzbarem Wert sein. Nutzen Sie __slots__
, um den Speicher-Footprint Ihrer Objekte zu reduzieren und die Effizienz Ihres Python-Codes zu steigern.