OOP - Slots
Inhalt
OOP - Slots¶
Bemerkung
Der eigentliche Zweck für die Entwicklung __slots__
war ein Performancegewinn zu erzielen. „Slotted Classes“ sind etwas schnell was die Zugriffsgeschwindigkeit auf Attribute betrifft. Weiters brauchen diese weniger Speicher.
Oft ist zu lese das __slots__
Klassen sicherer machen, da Attribute nicht mehr dynamisch erstellt werden können. Das ist zwar nicht falsch war aber nicht der ursprüngliche Grund für die Entwicklung von __slots__
.
Tipp
Normale Klassen¶
Normale Python Klassen erlauben das dynamische Erstellen von Attributen.
class Person():
def __init__(self,first_name,last_name):
self.first_name = first_name
self.last_name = last_name
p1 = Person("Max","Muster")
p1.__dict__
{'first_name': 'Max', 'last_name': 'Muster'}
Durch __dict__
erhalten wir die Instanzattribute in def Form eines Dictionary.
Python erlaubt uns nun das dynamisch hinzufügen von Instanzattributen.
p1.age = 21
p1.__dict__
{'first_name': 'Max', 'last_name': 'Muster', 'age': 21}
Wie wir sehen ist nun das Attribute age
hinzugefügt worden.
„Slotted“ Klassen¶
Die Verwendung von __slots__
verhindert die dynamische Erweiterung von Instanzattributen.
Das hat wie oben besprochen folgende Vorteile:
Diese Instanzen benötigen weniger Speicher
Der Zugriff auf Instanzattributen ist schneller.
Es können nicht unbeabsichtigt Attribute hinzugefügt werden.
class SlottedPerson():
__slots__ = ('first_name', 'last_name')
def __init__(self,first_name,last_name):
self.first_name = first_name
self.last_name = last_name
slotted_p1 = SlottedPerson("Max","Muster")
slotted_p1.__slots__
('first_name', 'last_name')
# ps.age = 21 # --> attribute error
# ps.__dict__ # --> attribute error
Performenz Vergleich¶
import sys
class Person():
def __init__(self,first_name,last_name):
self.first_name = first_name
self.last_name = last_name
p1 = Person("Max","Muster")
p1.__dict__
{'first_name': 'Max', 'last_name': 'Muster'}
class SlottedPerson():
__slots__ = ('first_name', 'last_name')
def __init__(self,first_name,last_name):
self.first_name = first_name
self.last_name = last_name
slotted_p1 = SlottedPerson("Max","Muster")
slotted_p1.__slots__
('first_name', 'last_name')
class PartSlottedPerson():
__slots__ = ('first_name','__dict__')
def __init__(self,first_name,last_name):
self.first_name = first_name
self.last_name = last_name
partslotted_p1 = PartSlottedPerson("Max","Muster")
print(partslotted_p1.__slots__)
print(partslotted_p1.__dict__)
('first_name', '__dict__')
{'last_name': 'Muster'}
Speicher¶
Bemerkung
pympler ist ein sehr gutes Python Modul zum Messen, Überwachen und Analysieren des Speicherverhaltens von Pyhton-Objekten.
Dieses Modul eignet sich besser Benutzer Klassen zu analysieren als sys
, ist jedoch nicht Teil der Standardbibliothek.
from pympler import asizeof
asizeof.asizeof(p1)
392
asizeof.asizeof(slotted_p1)
160
asizeof.asizeof(partslotted_p1)
328
Wir sehen ist die Instanz der „SlottedPerson“ Klasse deutlich kleiner als die „Person“ Klasse. pympler erlaubt uns noch mehr Details abzufragen.
print(asizeof.asized(p1, detail=1).format())
<__main__.Person object at 0x7f1e4c046d90> size=392 flat=48
__dict__ size=344 flat=104
__class__ size=0 flat=0
print(asizeof.asized(slotted_p1, detail=1).format())
<__main__.SlottedPerson object at 0x7f1e377d2940> size=160 flat=48
first_name size=56 flat=56
last_name size=56 flat=56
__class__ size=0 flat=0
print(asizeof.asized(partslotted_p1, detail=1).format())
<__main__.PartSlottedPerson object at 0x7f1e377d2a90> size=328 flat=48
__dict__ size=224 flat=104
first_name size=56 flat=56
__class__ size=0 flat=0
Hier sehen wir nun das der Overhead der der Klassen in unserem Fall gleich groß ist. Der Speicherunterschied erklärt sich also durch das Verwenden des __dict__
.
print(392-344) # total_size - dict_size
print(160-2*56) # total_size - attribute_size
print(328-224-56) # total_size - dict_size - attribute_size
48
48
48
Geschwindigkeiten¶
Zum Messen der Geschwindigkeit können wir das Modul timeit
aus der Standardbibliothek verwenden.
import timeit
Es werden die 3 Operation Schreiben, Lesen und das Löschen von Attributen durchgeführt.
def set_get_delete_func(obj):
def set_get_del():
obj.first_name = "Michael" # set
obj.last_name = "Maier" # set
obj.first_name # get
obj.last_name # get
del obj.first_name # del
del obj.last_name # del
return set_get_del
print(min(timeit.repeat(set_get_delete_func(p1))))
print(min(timeit.repeat(set_get_delete_func(slotted_p1))))
print(min(timeit.repeat(set_get_delete_func(partslotted_p1))))
print(max(timeit.repeat(set_get_delete_func(p1))))
print(max(timeit.repeat(set_get_delete_func(slotted_p1))))
print(max(timeit.repeat(set_get_delete_func(partslotted_p1))))
0.16847971300012432
0.12257865800347645
0.14542127300228458
0.1792634849989554
0.12501245699968422
0.16435634400113486
Wir können also sehen das Slotted Classes einen deutlichen Performancegewinn ermöglichen.
Fazit¶
Mit __slots__
lässt sich die Performance von Klassen verbessern, was das Ziel der Entwicklung war.
Es sei aber angemerkt, dass es mittlerweile einige Optionen in Python gibt um die Performance von Klassen zu verbessern.
Vererbung von __slots__
¶
Tipp
Die Vererbungsmechanismen von __slots__
sind durchaus komplex und werden im Kapitel Vererbung besprochen.