Antworten:
Was ist in Python der Zweck
__slots__
und in welchen Fällen sollte dies vermieden werden?
Mit dem speziellen Attribut __slots__
können Sie explizit angeben, welche Instanzattribute Ihre Objektinstanzen mit den erwarteten Ergebnissen haben sollen:
Die Platzersparnis ist von
__dict__
.__dict__
und __weakref__
Erstellen, wenn übergeordnete Klassen sie verweigern und Sie deklarieren __slots__
.Kleine Einschränkung: Sie sollten einen bestimmten Slot nur einmal in einem Vererbungsbaum deklarieren. Zum Beispiel:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python erhebt keine Einwände, wenn Sie dies falsch verstehen (dies sollte wahrscheinlich der Fall sein). Andernfalls treten möglicherweise keine Probleme auf, aber Ihre Objekte belegen mehr Platz als sie sonst sollten. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Dies liegt daran, dass der Slot-Deskriptor der Basis einen vom Falschen getrennten Slot hat. Dies sollte normalerweise nicht auftauchen, aber es könnte:
>>> w = Wrong()
>>> w.foo = 'foo'
>>> Base.foo.__get__(w)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: foo
>>> Wrong.foo.__get__(w)
'foo'
Die größte Einschränkung betrifft die Mehrfachvererbung - mehrere "übergeordnete Klassen mit nicht leeren Slots" können nicht kombiniert werden.
Um dieser Einschränkung Rechnung zu tragen, befolgen Sie die Best Practices: Berücksichtigen Sie die Abstraktion aller bis auf einen oder alle Eltern, von der ihre konkrete Klasse bzw. Ihre neue konkrete Klasse gemeinsam erben wird. Geben Sie den Abstraktionen leere Slots (genau wie abstrakte Basisklassen in der.) Standardbibliothek).
Ein Beispiel finden Sie im Abschnitt zur Mehrfachvererbung.
Damit Attribute benannt werden können, __slots__
um tatsächlich in Slots anstelle von a gespeichert zu werden __dict__
, muss eine Klasse von erben object
.
Um die Erstellung von a zu verhindern __dict__
, müssen Sie von erben object
und alle Klassen in der Vererbung müssen deklarieren, __slots__
und keine von ihnen kann einen '__dict__'
Eintrag haben.
Es gibt viele Details, wenn Sie weiterlesen möchten.
__slots__
: Schnellerer Zugriff auf Attribute.Der Schöpfer von Python, Guido van Rossum, gibt an, dass er tatsächlich __slots__
für einen schnelleren Attributzugriff erstellt hat.
Es ist trivial, einen messbar signifikanten schnelleren Zugriff nachzuweisen:
import timeit
class Foo(object): __slots__ = 'foo',
class Bar(object): pass
slotted = Foo()
not_slotted = Bar()
def get_set_delete_fn(obj):
def get_set_delete():
obj.foo = 'foo'
obj.foo
del obj.foo
return get_set_delete
und
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
Der Slot-Zugriff ist in Python 3.5 unter Ubuntu fast 30% schneller.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
In Python 2 unter Windows habe ich es ungefähr 15% schneller gemessen.
__slots__
: SpeichereinsparungenEin weiterer Zweck von __slots__
besteht darin, den Speicherplatz zu reduzieren, den jede Objektinstanz belegt.
In meinem eigenen Beitrag zur Dokumentation sind die Gründe dafür klar angegeben :
Der durch die Verwendung eingesparte Speicherplatz
__dict__
kann erheblich sein.
SQLAlchemy schreibt eine Menge Speichereinsparungen zu __slots__
.
Um dies zu überprüfen, verwendet die Anaconda-Distribution von Python 2.7 unter Ubuntu Linux mit guppy.hpy
(auch bekannt als heapy) und sys.getsizeof
die Größe einer Klasseninstanz ohne __slots__
Deklaration und sonst nichts 64 Byte. Das macht nicht enthalten die __dict__
. Vielen Dank an Python für die verzögerte Auswertung. Das __dict__
wird anscheinend erst ins Leben gerufen, wenn darauf verwiesen wird, aber Klassen ohne Daten sind normalerweise nutzlos. Beim __dict__
Aufrufen beträgt das Attribut zusätzlich mindestens 280 Byte.
Im Gegensatz dazu besteht eine Klasseninstanz mit __slots__
deklariert als ()
(keine Daten) nur aus 16 Bytes und 56 Gesamtbytes mit einem Element in Slots, 64 mit zwei.
Für 64-Bit-Python zeige ich den Speicherverbrauch in Bytes in Python 2.7 und 3.6 für __slots__
und __dict__
(keine Slots definiert) für jeden Punkt, an dem das Diktat in 3.6 wächst (mit Ausnahme der Attribute 0, 1 und 2):
Python 2.7 Python 3.6
attrs __slots__ __dict__* __slots__ __dict__* | *(no slots defined)
none 16 56 + 272† 16 56 + 112† | †if __dict__ referenced
one 48 56 + 272 48 56 + 112
two 56 56 + 272 56 56 + 112
six 88 56 + 1040 88 56 + 152
11 128 56 + 1040 128 56 + 240
22 216 56 + 3344 216 56 + 408
43 384 56 + 3344 384 56 + 752
Trotz kleinerer Dikte in Python 3 sehen wir, wie gut sich __slots__
Instanzen skalieren lassen, um Speicherplatz zu sparen, und das ist ein Hauptgrund, den Sie verwenden möchten __slots__
.
Beachten Sie zur Vollständigkeit meiner Notizen, dass im Namespace der Klasse einmalige Kosten pro Slot von 64 Byte in Python 2 und 72 Byte in Python 3 anfallen, da Slots Datendeskriptoren wie Eigenschaften verwenden, die als "Mitglieder" bezeichnet werden.
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:Um die Erstellung von a zu verweigern __dict__
, müssen Sie folgende Unterklassen erstellen object
:
class Base(object):
__slots__ = ()
jetzt:
>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'
Oder eine andere Klasse unterordnen, die definiert __slots__
class Child(Base):
__slots__ = ('a',)
und nun:
c = Child()
c.a = 'a'
aber:
>>> c.b = 'b'
Traceback (most recent call last):
File "<pyshell#42>", line 1, in <module>
c.b = 'b'
AttributeError: 'Child' object has no attribute 'b'
Damit __dict__
Schaffung während geschlitzten Objekte Subklassen, fügen Sie einfach '__dict__'
auf die __slots__
(beachten Sie, dass Schlitze bestellt werden, und Sie sollten nicht wiederholen Slots , die bereits in übergeordneten Klassen sind):
class SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
und
>>> swd.__dict__
{'c': 'c'}
Oder Sie müssen nicht einmal __slots__
in Ihrer Unterklasse deklarieren , und Sie verwenden weiterhin Slots der Eltern, ohne die Erstellung eines __dict__
:
class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
Und:
>>> ns.__dict__
{'b': 'b'}
Allerdings __slots__
kann zu Problemen führen , für Mehrfachvererbung:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Da das Erstellen einer untergeordneten Klasse von Eltern mit beiden nicht leeren Steckplätzen fehlschlägt:
>>> class Child(BaseA, BaseB): __slots__ = ()
Traceback (most recent call last):
File "<pyshell#68>", line 1, in <module>
class Child(BaseA, BaseB): __slots__ = ()
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
Wenn Sie auf dieses Problem stoßen, können Sie es einfach __slots__
von den Eltern entfernen oder, wenn Sie die Kontrolle über die Eltern haben, ihnen leere Slots geben oder Abstraktionen umgestalten:
from abc import ABC
class AbstractA(ABC):
__slots__ = ()
class BaseA(AbstractA):
__slots__ = ('a',)
class AbstractB(ABC):
__slots__ = ()
class BaseB(AbstractB):
__slots__ = ('b',)
class Child(AbstractA, AbstractB):
__slots__ = ('a', 'b')
c = Child() # no problem!
'__dict__'
, __slots__
um eine dynamische Zuordnung zu erhalten:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
und nun:
>>> foo = Foo()
>>> foo.boink = 'boink'
Mit '__dict__'
In-Slots verlieren wir einige der Größenvorteile, da wir eine dynamische Zuweisung haben und immer noch Slots für die Namen haben, die wir erwarten.
Wenn Sie von einem Objekt erben, das nicht mit einem Slot versehen ist, erhalten Sie bei der Verwendung dieselbe Semantik __slots__
- Namen, die __slots__
auf Slot-Werte verweisen, während andere Werte in die Instanz eingefügt werden __dict__
.
Vermeiden, __slots__
weil Sie in der Lage sein möchten, Attribute im laufenden Betrieb hinzuzufügen, ist eigentlich kein guter Grund - fügen "__dict__"
Sie einfach Ihre hinzu , __slots__
wenn dies erforderlich ist.
Sie können auch explizit hinzufügen __weakref__
, __slots__
wenn Sie diese Funktion benötigen.
Das eingebaute Namedtuple macht unveränderliche Instanzen, die sehr leicht sind (im Wesentlichen die Größe von Tupeln). Um jedoch die Vorteile zu nutzen, müssen Sie dies selbst tun, wenn Sie sie in Unterklassen unterteilen:
from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
"""MyNT is an immutable and lightweight object"""
__slots__ = ()
Verwendungszweck:
>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'
Der Versuch, ein unerwartetes Attribut zuzuweisen, führt zu einem, AttributeError
weil wir die Erstellung von Folgendem verhindert haben __dict__
:
>>> nt.quux = 'quux'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'
Sie können die __dict__
Erstellung zulassen, indem Sie sie weglassen __slots__ = ()
, aber Sie können nicht nicht leer __slots__
mit Subtypen von Tupeln verwenden.
Selbst wenn nicht leere Steckplätze für mehrere Eltern gleich sind, können sie nicht zusammen verwendet werden:
class Foo(object):
__slots__ = 'foo', 'bar'
class Bar(object):
__slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()
>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
Mit einem leeren __slots__
in den Eltern scheint die Flexibilität zu bieten, das Kind ermöglicht zu wählen , zu verhindern oder zu ermöglichen (durch Hinzufügen '__dict__'
dynamische Zuordnung zu erhalten, siehe Abschnitt oben) die Schaffung eines__dict__
:
class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'
Sie müssen nicht haben zu Slots - also , wenn Sie sie hinzufügen, und entfernen Sie sie später, es keine Probleme verursachen sollte.
Hier auf die Nerven gehen : Wenn Sie Mixins komponieren oder abstrakte Basisklassen verwenden , die nicht instanziiert werden sollen, __slots__
scheint ein Leerzeichen in diesen Eltern der beste Weg zu sein, um die Flexibilität für Unterklassen zu verbessern.
Um dies zu demonstrieren, erstellen wir zunächst eine Klasse mit Code, den wir unter Mehrfachvererbung verwenden möchten
class AbstractBase:
__slots__ = ()
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'
Wir könnten das Obige direkt verwenden, indem wir die erwarteten Slots erben und deklarieren:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
Aber das interessiert uns nicht, das ist eine triviale Einzelvererbung. Wir brauchen eine andere Klasse, von der wir möglicherweise auch erben, vielleicht mit einem lauten Attribut:
class AbstractBaseC:
__slots__ = ()
@property
def c(self):
print('getting c!')
return self._c
@c.setter
def c(self, arg):
print('setting c!')
self._c = arg
Wenn beide Basen nicht leere Slots hätten, könnten wir das Folgende nicht tun. (Wenn wir wollten, hätten wir tatsächlich AbstractBase
nicht leere Slots a und b vergeben und sie aus der folgenden Erklärung herauslassen können - es wäre falsch, sie zu belassen):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
Und jetzt haben wir Funktionalität von beiden über Mehrfachvererbung und können immer noch verweigern __dict__
und __weakref__
instanziieren:
>>> c = Concretion('a', 'b')
>>> c.c = c
setting c!
>>> c.c
getting c!
Concretion('a', 'b')
>>> c.d = 'd'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Concretion' object has no attribute 'd'
__class__
Zuweisung mit einer anderen Klasse durchführen möchten, die sie nicht hat (und Sie können sie nicht hinzufügen), es sei denn, die Steckplatzlayouts sind identisch. (Ich bin sehr daran interessiert zu erfahren, wer dies tut und warum.)Möglicherweise können Sie weitere Vorbehalte aus dem Rest der __slots__
Dokumentation herausarbeiten (die 3.7-Entwicklerdokumente sind die aktuellsten) , zu denen ich in letzter Zeit wichtige Beiträge geleistet habe.
Die aktuellen Top-Antworten zitieren veraltete Informationen und sind ziemlich handgewellt und verfehlen die Marke in einigen wichtigen Punkten.
__slots__
Instanziieren vieler Objekte verwenden"Ich zitiere:
"Sie möchten verwenden,
__slots__
wenn Sie viele (Hunderte, Tausende) Objekte derselben Klasse instanziieren möchten ."
Abstrakte Basisklassen, beispielsweise aus dem collections
Modul, werden nicht instanziiert, sondern __slots__
für sie deklariert.
Warum?
Wenn ein Benutzer die Erstellung verweigern __dict__
oder __weakref__
erstellen möchte , dürfen diese Dinge in den übergeordneten Klassen nicht verfügbar sein.
__slots__
trägt zur Wiederverwendbarkeit beim Erstellen von Schnittstellen oder Mixins bei.
Es ist wahr, dass viele Python-Benutzer nicht für die Wiederverwendbarkeit schreiben, aber wenn Sie dies tun, ist es wertvoll, die Option zu haben, unnötige Speicherplatznutzung zu verweigern.
__slots__
bricht das Beizen nichtWenn Sie ein geschlitztes Objekt beizen, kann es sein, dass es irreführend ist TypeError
:
>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
Das ist eigentlich falsch. Diese Nachricht stammt aus dem ältesten Protokoll, das die Standardeinstellung ist. Sie können das neueste Protokoll mit dem -1
Argument auswählen . In Python 2.7 wäre dies 2
(was in 2.3 eingeführt wurde) und in 3.6 ist es 4
.
>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
in Python 2.7:
>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
in Python 3.6
>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
Daher würde ich dies berücksichtigen, da es sich um ein gelöstes Problem handelt.
Der erste Absatz ist eine halb kurze Erklärung, halb eine Vorhersage. Hier ist der einzige Teil, der die Frage tatsächlich beantwortet
Die richtige Verwendung
__slots__
ist, um Platz in Objekten zu sparen. Anstatt ein dynamisches Diktat zu haben, mit dem Objekte jederzeit mit Attributen versehen werden können, gibt es eine statische Struktur, die nach der Erstellung keine Ergänzungen zulässt. Dies spart den Overhead eines Diktats für jedes Objekt, das Slots verwendet
Die zweite Hälfte ist Wunschdenken und falsch:
Obwohl dies manchmal eine nützliche Optimierung ist, wäre es völlig unnötig, wenn der Python-Interpreter dynamisch genug wäre, so dass er nur dann das Diktat benötigt, wenn das Objekt tatsächlich ergänzt wurde.
Python macht tatsächlich etwas Ähnliches und erstellt nur das, __dict__
wenn darauf zugegriffen wird, aber das Erstellen vieler Objekte ohne Daten ist ziemlich lächerlich.
Der zweite Absatz vereinfacht und übersieht tatsächliche Gründe, die vermieden werden sollten __slots__
. Das Folgende ist kein wirklicher Grund, Slots zu vermeiden (aus tatsächlichen Gründen siehe den Rest meiner Antwort oben):
Sie ändern das Verhalten von Objekten mit Slots auf eine Weise, die von Kontrollfreaks und statischen Tippern missbraucht werden kann.
Anschließend werden andere Möglichkeiten zur Erreichung dieses perversen Ziels mit Python erörtert, ohne dass etwas damit zu tun hat __slots__
.
Der dritte Absatz ist Wunschdenken. Zusammen handelt es sich meistens um unkonventionelle Inhalte, die der Antwortende nicht einmal verfasst hat und die zur Munition für Kritiker der Website beitragen.
Erstellen Sie einige normale Objekte und geschlitzte Objekte:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Instanziieren Sie eine Million von ihnen:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Inspizieren mit guppy.hpy().heap()
:
>>> guppy.hpy().heap()
Partition of a set of 2028259 objects. Total size = 99763360 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1000000 49 64000000 64 64000000 64 __main__.Foo
1 169 0 16281480 16 80281480 80 list
2 1000000 49 16000000 16 96281480 97 __main__.Bar
3 12284 1 987472 1 97268952 97 str
...
Greifen Sie auf die regulären Objekte und deren Objekte zu __dict__
und überprüfen Sie sie erneut:
>>> for f in foos:
... f.__dict__
>>> guppy.hpy().heap()
Partition of a set of 3028258 objects. Total size = 379763480 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1000000 33 280000000 74 280000000 74 dict of __main__.Foo
1 1000000 33 64000000 17 344000000 91 __main__.Foo
2 169 0 16281480 4 360281480 95 list
3 1000000 33 16000000 4 376281480 99 __main__.Bar
4 12284 0 987472 0 377268952 99 str
...
Dies steht im Einklang mit der Geschichte von Python aus der Vereinheitlichung von Typen und Klassen in Python 2.2
Wenn Sie einen integrierten Typ unterordnen, wird den Instanzen automatisch zusätzlicher Speicherplatz hinzugefügt, um
__dict__
und zu berücksichtigen__weakrefs__
. (Das__dict__
wird jedoch erst initialisiert, wenn Sie es verwenden. Sie sollten sich also keine Gedanken über den Speicherplatz machen, den ein leeres Wörterbuch für jede von Ihnen erstellte Instanz belegt.) Wenn Sie diesen zusätzlichen Speicherplatz nicht benötigen, können Sie den Ausdruck "__slots__ = []
" hinzufügen deine Klasse.
__slots__
. Ernsthaft! Vielen Dank!
__slots__
ungefähr einem Jahr zu den Python-Dokumenten beigetragen : github.com/python/cpython/pull/1819/files
Zitat Jacob Hallen :
Die richtige Verwendung
__slots__
ist, um Platz in Objekten zu sparen. Anstatt ein dynamisches Diktat zu haben, mit dem Objekte jederzeit mit Attributen versehen werden können, gibt es eine statische Struktur, die nach der Erstellung keine Ergänzungen zulässt. [Diese Verwendung von__slots__
eliminiert den Overhead eines Diktats für jedes Objekt.] Obwohl dies manchmal eine nützliche Optimierung ist, wäre es völlig unnötig, wenn der Python-Interpreter dynamisch genug wäre, so dass er das Diktat nur dann benötigt, wenn es tatsächlich Ergänzungen zum Objekt gibt Objekt.Leider haben Slots einen Nebeneffekt. Sie ändern das Verhalten von Objekten mit Slots auf eine Weise, die von Kontrollfreaks und statischen Tippern missbraucht werden kann. Das ist schlecht, weil die Kontrollfreaks die Metaklassen missbrauchen sollten und die statischen Tipp-Weenies Dekorateure missbrauchen sollten, da es in Python nur einen offensichtlichen Weg geben sollte, etwas zu tun.
CPython intelligent genug zu machen, um Platz zu sparen, ohne
__slots__
es zu sparen, ist ein großes Unterfangen, weshalb es wahrscheinlich (noch) nicht auf der Liste der Änderungen für P3k steht.
__slots__
behandelt nicht die gleichen Probleme wie die statische Eingabe. In C ++ wird beispielsweise nicht die Deklaration einer Mitgliedsvariablen eingeschränkt, sondern die Zuweisung eines unbeabsichtigten Typs (und des erzwungenen Compilers) zu dieser Variablen. Ich dulde nicht die Verwendung von __slots__
, nur an dem Gespräch interessiert. Vielen Dank!
Sie möchten verwenden, __slots__
wenn Sie viele (Hunderte, Tausende) Objekte derselben Klasse instanziieren möchten . __slots__
existiert nur als Speicheroptimierungswerkzeug.
Es wird dringend davon abgeraten, die __slots__
Attributerstellung einzuschränken.
Das Beizen von Objekten mit __slots__
funktioniert nicht mit dem Standard (ältesten) Beizprotokoll. Es ist erforderlich, eine spätere Version anzugeben.
Einige andere Introspektionsmerkmale von Python können ebenfalls nachteilig beeinflusst werden.
Jedes Python-Objekt verfügt über ein __dict__
Attribut, bei dem es sich um ein Wörterbuch handelt, das alle anderen Attribute enthält. Beispiel: Wenn Sie self.attr
Python eingeben, ist dies tatsächlich der Fall self.__dict__['attr']
. Wie Sie sich vorstellen können, benötigt die Verwendung eines Wörterbuchs zum Speichern von Attributen zusätzlichen Speicherplatz und Zeit für den Zugriff darauf.
Wenn Sie jedoch verwenden __slots__
, hat jedes für diese Klasse erstellte Objekt kein __dict__
Attribut. Stattdessen erfolgt der gesamte Attributzugriff direkt über Zeiger.
Wenn Sie also eine Struktur im C-Stil anstelle einer vollwertigen Klasse wünschen, können Sie die __slots__
Größe der Objekte komprimieren und die Zugriffszeit für Attribute reduzieren. Ein gutes Beispiel ist eine Point-Klasse mit den Attributen x & y. Wenn Sie viele Punkte haben, können Sie versuchen, __slots__
etwas Speicherplatz zu sparen.
__slots__
definiert ist nicht wie eine C-Struktur. Es gibt ein Wörterbuch auf Klassenebene, das Attributnamen Indizes zuordnet, andernfalls wäre Folgendes nicht möglich: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
Ich denke wirklich, dass diese Antwort geklärt werden sollte (ich kann das tun, wenn Sie möchten). Ich bin mir auch nicht sicher, ob instance.__hidden_attributes[instance.__class__[attrname]]
das schneller ist als instance.__dict__[attrname]
.
Zusätzlich zu den anderen Antworten finden Sie hier ein Beispiel für die Verwendung von __slots__
:
>>> class Test(object): #Must be new-style class!
... __slots__ = ['x', 'y']
...
>>> pt = Test()
>>> dir(pt)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__',
'__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__slots__', '__str__', 'x', 'y']
>>> pt.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: x
>>> pt.x = 1
>>> pt.x
1
>>> pt.z = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'z'
>>> pt.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__dict__'
>>> pt.__slots__
['x', 'y']
Für die Implementierung __slots__
ist nur eine zusätzliche Zeile erforderlich (und Ihre Klasse wird zu einer Klasse neuen Stils, sofern dies noch nicht geschehen ist). Auf diese Weise können Sie den Speicherbedarf dieser Klassen um das Fünffache reduzieren , auf Kosten der Notwendigkeit, benutzerdefinierten Pickle-Code zu schreiben, falls dies erforderlich wird.
Slots sind sehr nützlich für Bibliotheksaufrufe, um den "Named Method Dispatch" bei Funktionsaufrufen zu eliminieren. Dies wird in der SWIG- Dokumentation erwähnt . Für Hochleistungsbibliotheken, die den Funktionsaufwand für häufig aufgerufene Funktionen mithilfe von Slots reduzieren möchten, ist dies viel schneller.
Dies hängt möglicherweise nicht direkt mit der OP-Frage zusammen. Es bezieht sich mehr auf das Erstellen von Erweiterungen als auf die Verwendung der Slots- Syntax für ein Objekt. Aber es hilft, das Bild für die Verwendung von Slots und einige der Gründe dafür zu vervollständigen.
Ein Attribut einer Klasseninstanz hat drei Eigenschaften: die Instanz, den Namen des Attributs und den Wert des Attributs.
Beim regulären Attributzugriff fungiert die Instanz als Wörterbuch, und der Name des Attributs fungiert als Schlüssel in diesem Wörterbuch, der nach dem Wert sucht.
Instanz (Attribut) -> Wert
Beim Zugriff auf __slots__ fungiert der Name des Attributs als Wörterbuch und die Instanz als Schlüssel im Wörterbuch, der nach dem Wert sucht.
Attribut (Instanz) -> Wert
Im Flyweight-Muster fungiert der Name des Attributs als Wörterbuch und der Wert als Schlüssel in diesem Wörterbuch, das die Instanz nachschlägt.
Attribut (Wert) -> Instanz
__slots__
?
Ein sehr einfaches Beispiel für ein __slot__
Attribut.
__slots__
Wenn ich kein __slot__
Attribut in meiner Klasse habe, kann ich meinen Objekten neue Attribute hinzufügen.
class Test:
pass
obj1=Test()
obj2=Test()
print(obj1.__dict__) #--> {}
obj1.x=12
print(obj1.__dict__) # --> {'x': 12}
obj1.y=20
print(obj1.__dict__) # --> {'x': 12, 'y': 20}
obj2.x=99
print(obj2.__dict__) # --> {'x': 99}
Wenn Sie sich das obige Beispiel ansehen, können Sie sehen, dass obj1 und obj2 ihre eigenen x- und y- Attribute haben und Python auch ein dict
Attribut für jedes Objekt erstellt hat ( obj1 und obj2 ).
Nehmen wir an, wenn meine Klasse - Test Tausende solcher Objekte hat? Das Erstellen eines zusätzlichen Attributs dict
für jedes Objekt verursacht viel Overhead (Speicher, Rechenleistung usw.) in meinem Code.
__slots__
Im folgenden Beispiel enthält meine Klasse Test ein__slots__
Attribut. Jetzt kann ich meinen Objekten keine neuen Attribute hinzufügen (außer Attributen x
) und Python erstellt kein dict
Attribut mehr. Dadurch entfällt der Overhead für jedes Objekt, der bei vielen Objekten erheblich werden kann.
class Test:
__slots__=("x")
obj1=Test()
obj2=Test()
obj1.x=12
print(obj1.x) # --> 12
obj2.x=99
print(obj2.x) # --> 99
obj1.y=28
print(obj1.y) # --> AttributeError: 'Test' object has no attribute 'y'
Eine andere etwas undurchsichtige Verwendung __slots__
ist das Hinzufügen von Attributen zu einem Objekt-Proxy aus dem ProxyTypes-Paket, das früher Teil des PEAK-Projekts war. Sein ObjectWrapper
ermöglicht es Ihnen , ein anderes Objekt zu Proxy, aber abfangen alle Interaktionen mit dem Proxy - Objekt. Es wird nicht sehr häufig verwendet (und bietet keine Python 3-Unterstützung), aber wir haben es verwendet, um einen thread-sicheren Blockierungs-Wrapper um eine auf Tornado basierende asynchrone Implementierung zu implementieren, der den gesamten Zugriff auf das Proxy-Objekt über ioloop mithilfe von thread-safe abprallt concurrent.Future
Objekte zum Synchronisieren und Zurückgeben von Ergebnissen.
Standardmäßig erhalten Sie bei jedem Attributzugriff auf das Proxy-Objekt das Ergebnis des Proxy-Objekts. Wenn Sie dem Proxy-Objekt ein Attribut hinzufügen müssen, __slots__
kann es verwendet werden.
from peak.util.proxies import ObjectWrapper
class Original(object):
def __init__(self):
self.name = 'The Original'
class ProxyOriginal(ObjectWrapper):
__slots__ = ['proxy_name']
def __init__(self, subject, proxy_name):
# proxy_info attributed added directly to the
# Original instance, not the ProxyOriginal instance
self.proxy_info = 'You are proxied by {}'.format(proxy_name)
# proxy_name added to ProxyOriginal instance, since it is
# defined in __slots__
self.proxy_name = proxy_name
super(ProxyOriginal, self).__init__(subject)
if __name__ == "__main__":
original = Original()
proxy = ProxyOriginal(original, 'Proxy Overlord')
# Both statements print "The Original"
print "original.name: ", original.name
print "proxy.name: ", proxy.name
# Both statements below print
# "You are proxied by Proxy Overlord", since the ProxyOriginal
# __init__ sets it to the original object
print "original.proxy_info: ", original.proxy_info
print "proxy.proxy_info: ", proxy.proxy_info
# prints "Proxy Overlord"
print "proxy.proxy_name: ", proxy.proxy_name
# Raises AttributeError since proxy_name is only set on
# the proxy object
print "original.proxy_name: ", proxy.proxy_name
Sie haben - im Wesentlichen - keine Verwendung für __slots__
.
Für die Zeit, in der Sie glauben, dass Sie sie benötigen __slots__
, möchten Sie tatsächlich Lightweight- oder Flyweight- Designmuster verwenden. Dies sind Fälle, in denen Sie keine reinen Python-Objekte mehr verwenden möchten. Stattdessen möchten Sie einen Python-Objekt-ähnlichen Wrapper um ein Array, eine Struktur oder ein Numpy-Array.
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= value
Der klassenähnliche Wrapper hat keine Attribute - er bietet nur Methoden, die auf die zugrunde liegenden Daten wirken. Die Methoden können auf Klassenmethoden reduziert werden. In der Tat könnte es auf nur Funktionen reduziert werden, die mit dem zugrunde liegenden Datenarray arbeiten.
__slots__
?
__slots__
sind beide Optimierungstechniken, um Speicherplatz zu sparen. __slots__
zeigt Vorteile, wenn Sie viele, viele Objekte sowie Flyweight Design-Muster haben. Beide lösen das gleiche Problem.
__slots__
ist das wirklich die Antwort, und wie Evgeni betont, kann sie als einfacher nachträglicher Gedanke hinzugefügt werden (z. B. können Sie sich zuerst auf die Korrektheit konzentrieren und dann die Leistung steigern).
Die ursprüngliche Frage betraf allgemeine Anwendungsfälle, nicht nur das Gedächtnis. Daher sollte hier erwähnt werden, dass Sie auch beim Instanziieren großer Objektmengen eine bessere Leistung erzielen - interessant, z. B. beim Parsen großer Dokumente in Objekte oder aus einer Datenbank.
Hier ist ein Vergleich des Erstellens von Objektbäumen mit einer Million Einträgen unter Verwendung von Slots und ohne Slots. Als Referenz auch die Leistung bei der Verwendung von einfachen Diktaten für die Bäume (Py2.7.10 unter OSX):
********** RUN 1 **********
1.96036410332 <class 'css_tree_select.element.Element'>
3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
2.90828204155 dict
********** RUN 2 **********
1.77050495148 <class 'css_tree_select.element.Element'>
3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
2.84120798111 dict
********** RUN 3 **********
1.84069895744 <class 'css_tree_select.element.Element'>
3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
2.59615707397 dict
********** RUN 4 **********
1.75041103363 <class 'css_tree_select.element.Element'>
3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
2.70941114426 dict
Testklassen (ident, getrennt von Slots):
class Element(object):
__slots__ = ['_typ', 'id', 'parent', 'childs']
def __init__(self, typ, id, parent=None):
self._typ = typ
self.id = id
self.childs = []
if parent:
self.parent = parent
parent.childs.append(self)
class ElementNoSlots(object): (same, w/o slots)
Testcode, ausführlicher Modus:
na, nb, nc = 100, 100, 100
for i in (1, 2, 3, 4):
print '*' * 10, 'RUN', i, '*' * 10
# tree with slot and no slot:
for cls in Element, ElementNoSlots:
t1 = time.time()
root = cls('root', 'root')
for i in xrange(na):
ela = cls(typ='a', id=i, parent=root)
for j in xrange(nb):
elb = cls(typ='b', id=(i, j), parent=ela)
for k in xrange(nc):
elc = cls(typ='c', id=(i, j, k), parent=elb)
to = time.time() - t1
print to, cls
del root
# ref: tree with dicts only:
t1 = time.time()
droot = {'childs': []}
for i in xrange(na):
ela = {'typ': 'a', id: i, 'childs': []}
droot['childs'].append(ela)
for j in xrange(nb):
elb = {'typ': 'b', id: (i, j), 'childs': []}
ela['childs'].append(elb)
for k in xrange(nc):
elc = {'typ': 'c', id: (i, j, k), 'childs': []}
elb['childs'].append(elc)
td = time.time() - t1
print td, 'dict'
del droot
class Child(BaseA, BaseB): __slots__ = ('a', 'b')
Beispiel mit den Empy-Slot-Eltern nicht verstanden. Warum wird hier ein fürdictproxy
erstellt, anstatt einAttributeError
für zu erhöhenc
?