it-swarm.com.de

Verwendung von __slots__?

Was ist der Zweck von __slots__ in Python - insbesondere in Bezug darauf, wann ich es verwenden möchte und wann nicht?

657
Jeb

Was ist der Zweck von __slots__ In Python und in welchen Fällen sollte dies vermieden werden?

TLDR:

Mit dem speziellen Attribut __slots__ Können Sie explizit angeben, welche Instanzattribute Ihre Objektinstanzen haben sollen, mit den erwarteten Ergebnissen:

  1. schneller Attributzugriff.
  2. Platzersparnis im Speicher.

Die Platzersparnis ergibt sich aus

  1. Speichern von Wertereferenzen in Slots anstelle von __dict__.
  2. Die Erstellung von __dict__ Und __weakref__ Wird verweigert, wenn übergeordnete Klassen sie verweigern und Sie __slots__ Deklarieren.

Schnelle Vorsichtsmaßnahmen

Kleine Einschränkung, Sie sollten einen bestimmten Slot in einem Vererbungsbaum nur einmal 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 hat keine Einwände, wenn Sie dies falsch verstehen (dies sollte wahrscheinlich der Fall sein). Möglicherweise treten keine anderen Probleme auf, aber Ihre Objekte belegen mehr Speicherplatz als sie sollten.

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(64, 80)

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 empfohlenen Vorgehensweisen: Ziehen Sie alle Abstraktionen mit Ausnahme der Abstraktionen eines oder aller Eltern heraus, von denen ihre konkrete Klasse bzw. Ihre neue konkrete Klasse gemeinsam erbt - und geben Sie der Abstraktion (en) leere Slots (genau wie bei abstrakten Basisklassen in der Standardbibliothek).

Ein Beispiel finden Sie weiter unten im Abschnitt zur Mehrfachvererbung.

Bedarf:

  • Damit Attribute mit dem Namen __slots__ Tatsächlich in Slots anstelle von __dict__ Gespeichert werden, muss eine Klasse von object erben.

  • Um die Erstellung eines __dict__ Zu verhindern, müssen Sie von object erben, und alle Klassen in der Vererbung müssen __slots__ Deklarieren, und keiner von ihnen kann einen '__dict__' Haben. Eintrag.

Es gibt viele Details, wenn Sie weiterlesen möchten.

Gründe für die Verwendung von __slots__: Schnellerer Attributzugriff.

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, messbar signifikanten schnelleren Zugriff zu demonstrieren:

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 Schlitzzugriff 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.

Gründe für die Verwendung von __slots__: Speichereinsparung

Ein weiterer Zweck von __slots__ Besteht darin, den Speicherplatz zu reduzieren, den jede Objektinstanz beansprucht.

In meinem eigenen Beitrag zur Dokumentation sind die Gründe dafür klar angegeben :

Der Platz, der mit __dict__ Eingespart wurde, kann erheblich sein.

SQLAlchemy weist eine Menge Speichereinsparungen auf __slots__ Zu.

Um dies zu überprüfen, verwenden Sie die Anaconda-Distribution von Python 2.7 unter Ubuntu Linux mit guppy.hpy (Auch bekannt als heapy) und sys.getsizeof, Der Größe einer Klasseninstanz ohne __slots__ Deklariert und nichts anderes ist 64 Bytes. Das schließt nicht den __dict__ Ein. Vielen Dank Python für die faulere Auswertung. Der __dict__ Wird anscheinend erst aufgerufen, wenn darauf verwiesen wird, aber Klassen ohne Daten sind normalerweise nutzlos. Beim Aufruf beträgt das Attribut __dict__ Zusätzlich mindestens 280 Byte.

Im Gegensatz dazu hat eine Klasseninstanz mit __slots__ Als () (Keine Daten) nur 16 Byte und 56 Gesamtbytes mit einem Element in Slots, 64 mit zwei.

Für 64-Bit-Python veranschauliche ich den Speicherverbrauch in Byte in Python 2.7 und 3.6 für __slots__ Und __dict__ (Keine Slots definiert) für jeden Punkt, an dem das Diktat wächst in 3.6 (mit Ausnahme von 0, 1 und 2 Attributen):

       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 also, wie gut __slots__ Sich Instanzen skalieren lassen, um Speicherplatz zu sparen. Dies ist ein Hauptgrund, warum Sie __slots__.

Um meine Notizen zu vervollständigen, beachten Sie, dass es einmalige Kosten pro Slot im Namespace der Klasse von 64 Bytes in Python 2 und 72 Bytes in Python 3 gibt, weil Slots Verwenden Sie Datenbeschreibungen wie Eigenschaften, die als "Mitglieder" bezeichnet werden.

>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72

Demonstration von __slots__:

Um die Erstellung eines __dict__ Zu verweigern, müssen Sie die Unterklasse object festlegen:

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 Unterklasse einer anderen Klasse, die __slots__ Definiert.

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'

Um die Erstellung von __dict__ Zu ermöglichen, während Objekte mit Slots in Unterklassen unterteilt werden, fügen Sie einfach '__dict__' Zu __slots__ Hinzu (beachten Sie, dass die Slots sortiert sind und Sie Slots, die bereits in übergeordneten Klassen enthalten sind, nicht wiederholen sollten ):

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 werden weiterhin Slots von den Eltern verwenden, aber die Erstellung eines __dict__ Nicht einschränken:

class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'

Und:

>>> ns.__dict__
{'b': 'b'}

__slots__ Kann jedoch Probleme bei der Mehrfachvererbung verursachen:

class BaseA(object): 
    __slots__ = ('a',)

class BaseB(object): 
    __slots__ = ('b',)

Da das Erstellen einer untergeordneten Klasse von Eltern mit beiden nicht leeren Slots 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 könnte einfach __slots__ Von den Eltern entfernen, oder wenn Sie die Kontrolle über die Eltern haben, geben Sie ihnen leere Slots oder refactor zu Abstraktionen:

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!

Fügen Sie '__dict__' Zu __slots__ Hinzu, um eine dynamische Zuweisung 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 Slots versehen ist, erhalten Sie dieselbe Semantik, wenn Sie __slots__ Verwenden - Namen, die sich in __slots__ Befinden, verweisen auf Slot-Werte, während andere Werte gesetzt werden in der Instanz __dict__.

Das Vermeiden von __slots__, Weil Sie Attribute direkt hinzufügen möchten, ist eigentlich kein guter Grund - fügen Sie einfach "__dict__" Zu Ihrem __slots__ Hinzu, wenn dies erforderlich ist.

Sie können __weakref__ Auch explizit zu __slots__ Hinzufügen, wenn Sie diese Funktion benötigen.

Legen Sie fest, dass Tupel leer ist, wenn Sie ein benanntes Tupel unterordnen:

Das NamedTuple-Build erstellt unveränderliche Instanzen, die sehr kompakt sind (im Wesentlichen die Größe von Tupeln). Um die Vorteile zu erzielen, müssen Sie dies jedoch 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, löst eine AttributeError aus, da wir die Erstellung von __dict__ Verhindert haben:

>>> nt.quux = 'quux'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'

Sie können können die Erstellung von __dict__ Zulassen, indem Sie __slots__ = () weglassen, aber Sie können nicht leeren __slots__ Mit Subtypen von verwenden Tupel.

Größte Einschränkung: Mehrfachvererbung

Auch wenn nicht leere Slots 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

Die Verwendung eines leeren __slots__ Im übergeordneten Element scheint die größte Flexibilität zu bieten: das Kind kann wählen, ob es eine dynamische Zuweisung verhindern oder zulassen soll (durch Hinzufügen von '__dict__', Siehe Abschnitt oben) die Erstellung 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 haben nicht haben Slots - wenn Sie diese also hinzufügen und später entfernen, sollte dies keine Probleme verursachen.

Hier auf einen Ast gehen: Wenn Sie Mixins komponieren oder abstrakte Basisklassen verwenden , Ein leerer __slots__ in diesen Eltern scheint der beste Weg zu sein, um Subklassen Flexibilität zu bieten.

Zur Veranschaulichung 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 nutzen, indem wir die erwarteten Slots erben und deklarieren:

class Foo(AbstractBase):
    __slots__ = 'a', 'b'

Aber das interessiert uns nicht, das ist triviale Einzelvererbung, wir brauchen eine andere Klasse, von der wir möglicherweise auch erben, vielleicht mit einem verrauschten 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. (Tatsächlich hätten wir, wenn wir wollten, AbstractBase nicht leere Slots a und b vergeben und sie aus der nachstehenden Deklaration herausgelassen - sie einzulassen, wäre falsch.)

class Concretion(AbstractBase, AbstractBaseC):
    __slots__ = 'a b _c'.split()

Und jetzt haben wir Funktionen von beiden über Mehrfachvererbung und können weiterhin die Instanziierung von __dict__ Und __weakref__ Verweigern:

>>> 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'

Andere Fälle, um Slots zu vermeiden:

  • Vermeiden Sie sie, wenn Sie eine __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.)
  • Vermeiden Sie sie, wenn Sie Buildins mit variabler Länge wie long, Tuple oder str in Unterklassen einteilen und ihnen Attribute hinzufügen möchten.
  • Vermeiden Sie sie, wenn Sie darauf bestehen, Standardwerte über Klassenattribute für Instanzvariablen bereitzustellen.

Möglicherweise können Sie weitere Einschränkungen aus der Dokumentation __slots__herausfiltern (die 3.7-Entwicklerdokumente sind die aktuellsten) , die ich in letzter Zeit bedeutend gemacht habe Beiträge zu.

Kritik anderer Antworten

Die aktuellen Top-Antworten zitieren veraltete Informationen und sind ziemlich wellenförmig und verfehlen die Marke in einigen wichtigen Punkten.

Verwenden Sie nicht "nur __slots__, Wenn Sie viele Objekte instanziieren"

Ich zitiere:

"Sie sollten __slots__ Verwenden, wenn Sie viele (Hunderte, Tausende) Objekte derselben Klasse instanziieren möchten."

Abstrakte Basisklassen, beispielsweise aus dem Modul collections, werden nicht instanziiert, jedoch __slots__ Für sie deklariert.

Warum?

Wenn ein Benutzer die Erstellung von __dict__ Oder __weakref__ Ablehnen möchte, dürfen diese Elemente nicht in den übergeordneten Klassen verfügbar sein.

__slots__ Trägt zur Wiederverwendbarkeit beim Erstellen von Interfaces oder Mixins bei.

Es ist richtig, dass viele Python Benutzer nicht aus Gründen der Wiederverwendbarkeit schreiben, aber wenn Sie dies tun, ist es hilfreich, die Option zu haben, unnötigen Speicherplatzverbrauch zu verweigern.

__slots__ Bricht das Beizen nicht ab

Beim Beizen eines geschlitzten Objekts kann es zu einer irreführenden Beschwerde kommen 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. Dies ist die Standardeinstellung. Sie können das neueste Protokoll mit dem Argument -1 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>

Also ich würde das im Hinterkopf behalten, da es ein gelöstes Problem ist.

Kritik der (bis 2. Oktober 2016) akzeptierten Antwort

Der erste Absatz ist halb kurz erklärt, halb vorausschauend. Hier ist der einzige Teil, der die Frage tatsächlich beantwortet

Die richtige Verwendung von __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 das Hinzufügen nach dem Erstellen nicht zulässt. Dies spart den Overhead eines Diktats für jedes Objekt, das Slots verwendet

Die zweite Hälfte ist Wunschdenken und daneben:

Obwohl dies manchmal eine nützliche Optimierung ist, wäre es völlig unnötig, wenn der Python -Interpreter so dynamisch wäre, dass er das Diktat nur dann benötigt, wenn tatsächlich Ergänzungen zum Objekt vorhanden sind.

Python macht tatsächlich etwas Ähnliches: Es wird nur der __dict__ Erstellt, wenn darauf zugegriffen wird, aber es ist ziemlich lächerlich, viele Objekte ohne Daten zu erstellen.

Im zweiten Absatz werden die tatsächlichen Gründe zur Vermeidung von __slots__ Zu stark vereinfacht und übersehen. Das Folgende ist nicht ein wirklicher Grund, Slots zu vermeiden (aus tatsächlichen Gründen, siehe den Rest meiner Antwort oben.):

Sie ändern das Verhalten der Objekte, die über Slots verfügen, auf eine Weise, die von Kontrollfreaks und statischen Tippfreaks missbraucht werden kann.

Anschließend werden andere Möglichkeiten zur Erreichung dieses perversen Ziels mit Python besprochen, ohne dass etwas mit __slots__ Zu tun hat.

Der dritte Absatz ist mehr Wunschdenken. Zusammengenommen handelt es sich meistens um nicht alltägliche Inhalte, die der Antwortende nicht einmal verfasst hat, und sie tragen zur Munition für Kritiker der Website bei.

Belege für die Speichernutzung

Erstellen Sie einige normale Objekte und geschlitzte Objekte:

>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()

Instanziiere eine Million von ihnen:

>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]

Prüfen Sie 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 __dict__ Zu und überprüfen 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, von bis zum Vereinigen von Typen und Klassen in Python 2.2 .

Wenn Sie eine Unterklasse für einen integrierten Typ festlegen, wird den Instanzen automatisch zusätzlicher Speicherplatz hinzugefügt, um __dict__ Und __weakrefs__ Aufzunehmen. (Der __dict__ Wird erst initialisiert, wenn Sie ihn verwenden. Sie sollten sich also nicht um den Speicherplatz kümmern, 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 dies tun Fügen Sie der Klasse den Ausdruck "__slots__ = []" hinzu.

816
Aaron Hall

Zitat Jacob Hallen :

Die richtige Verwendung von __slots__ soll Platz in Objekten sparen. Anstatt ein dynamisches Diktat zu haben, mit dem Objekte jederzeit mit Attributen versehen werden können, gibt es eine statische Struktur, die das Hinzufügen nach dem Erstellen nicht zulässt. [Diese Verwendung von __slots__ beseitigt 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, um nur das Diktat zu benötigen wenn es tatsächlich Ergänzungen zum Objekt gab.

Leider gibt es einen Nebeneffekt bei Slots. Sie ändern das Verhalten der Objekte, die über Slots verfügen, auf eine Weise, die von Kontrollfreaks und statischen Tippfreaks missbraucht werden kann. Das ist schlecht, weil die Kontrollfreaks die Metaklassen missbrauchen sollten und die statischen Tippfehler die Dekorateure missbrauchen sollten, da es in Python nur einen offensichtlichen Weg geben sollte, etwas zu tun.

CPython intelligent genug machen, um Platz zu sparen, ohne __slots__ ist ein großes Unterfangen, weshalb es wahrscheinlich (noch) nicht auf der Änderungsliste für P3k steht.

262
Jeff Bauer

Sie möchten __slots__ wenn Sie eine Menge (Hunderte, Tausende) von Objekten derselben Klasse instanziieren wollen. __slots__ existiert nur als Werkzeug zur Speicheroptimierung.

Es wird dringend davon abgeraten, __slots__, um die Attributerstellung einzuschränken, und im Allgemeinen möchten Sie dies vermeiden, da es zusammen mit einigen anderen Introspektionsfunktionen von Python die Funktion pickle bricht.

120
Ryan

Jedes python Objekt hat ein __dict__ Attribut, das alle anderen Attribute enthält. Wenn Sie zB self.attr python Tatsächlich wird self.__dict__['attr'] ausgeführt. Wie Sie sich vorstellen können, benötigt die Verwendung eines Wörterbuchs zum Speichern von Attributen etwas mehr Platz und Zeit, um darauf zuzugreifen.

Wenn Sie jedoch __slots__ Verwenden, hat jedes für diese Klasse erstellte Objekt kein __dict__ - Attribut. Stattdessen erfolgt der gesamte Attributzugriff direkt über Zeiger.

Wenn Sie also eine C-Stil-Struktur anstelle einer vollwertigen Klasse wünschen, können Sie __slots__ Verwenden, um die Größe der Objekte zu komprimieren und die Attributzugriffszeit zu reduzieren. Ein gutes Beispiel ist eine Point-Klasse mit den Attributen x & y. Wenn Sie viele Punkte haben, können Sie versuchen, __slots__ Zu verwenden, um Speicherplatz zu sparen.

58
Suraj

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']

Also, um __slots__, es wird nur eine zusätzliche Zeile benötigt (und Ihre Klasse wird zu einer Klasse neuen Stils, wenn dies noch nicht geschehen ist). Auf diese Weise können Sie den Speicherbedarf dieser Klassen um das Fünffache verringern auf Kosten der Notwendigkeit, benutzerdefinierten Pickle-Code zu schreiben, falls dies erforderlich wird.

20
Evgeni Sergeev

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, die Slots verwenden, reduzieren möchten, ist dies viel schneller.

Dies hängt möglicherweise nicht direkt mit der Frage des OP 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 dahinter zu vervollständigen.

11
Demolishun

Ein Attribut einer Klasseninstanz hat drei Eigenschaften: die Instanz, den Namen des Attributs und den Wert des Attributs.

Bei regulärem Attributzugriff fungiert die Instanz als Wörterbuch, und der Name des Attributs fungiert als Schlüssel für die Suche in diesem Wörterbuch up value.

Instanz (Attribut) -> Wert

In __ slots__ access fungiert der Name des Attributs als Wörterbuch und die Instanz als Schlüssel im nachschlagenden Wörterbuch Wert.

Attribut (Instanz) -> Wert

In flyweight pattern fungiert der Name des Attributs als Wörterbuch und der Wert als Schlüssel für das nachschlagen des Wörterbuchs die Instanz.

Attribut (Wert) -> Instanz

7

Eine weitere etwas dunkle Verwendung von __slots__ dient zum Hinzufügen von Attributen zu einem Objekt-Proxy aus dem ProxyTypes-Paket, das zuvor Teil des PEAK-Projekts war. Mit ObjectWrapper können Sie ein anderes Objekt als Proxy verwenden, aber alle Interaktionen mit dem Proxy-Objekt abfangen. Es wird nicht sehr häufig verwendet (und es gibt keine Python 3-Unterstützung)), aber wir haben es verwendet, um einen thread-sicheren Blocking-Wrapper um eine asynchrone Implementierung auf der Basis von Tornado zu implementieren, der den gesamten Zugriff auf den Proxy abprallt Objekt durch den Ioloop, mit thread-safe 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 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 __== "__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
2
NeilenMarais

Ein sehr einfaches Beispiel für das Attribut __slot__.

Problem: Ohne __slots__

Wenn meine Klasse kein Attribut __slot__ Enthält, 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 und haben python hat auch ein dict-Attribut für jedes Objekt erstellt (obj1 und obj2).

Angenommen, meine Klasse Test hat Tausende solcher Objekte? Das Erstellen eines zusätzlichen Attributs dict für jedes Objekt verursacht einen hohen Overhead (Speicher, Rechenleistung usw.) in meinem Code.

Lösung: Mit __slots__

Im folgenden Beispiel enthält meine Klasse Test das Attribut __slots__. Jetzt kann ich meinen Objekten keine neuen Attribute hinzufügen (außer dem Attribut x) und python erstellt kein dict-Attribut mehr. Dadurch wird der Aufwand für beseitigt jedes Objekt, was bedeutsam werden kann, wenn Sie viele Objekte haben.

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'
2
N Randhawa

Sie haben - im Wesentlichen - keine Verwendung für __slots__.

Für die Zeit, in der du denkst, du könntest __slots__, Sie möchten tatsächlich Lightweight oder Flyweight Designmuster verwenden. In diesen Fällen möchten Sie keine reinen Python -Objekte mehr verwenden. Stattdessen möchten Sie einen Python objektähnlichen Wrapper um ein Array, eine Struktur oder eine Zahl 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 stellt nur Methoden bereit, die auf die zugrunde liegenden Daten einwirken. Die Methoden können auf Klassenmethoden reduziert werden. Tatsächlich könnte es auf Funktionen reduziert werden, die mit dem zugrunde liegenden Array von Daten arbeiten.

1
S.Lott

Die ursprüngliche Frage betraf allgemeine Anwendungsfälle und nicht nur das Gedächtnis. Daher sollte hier erwähnt werden, dass Sie auch besser werden Leistung, wenn Sie große Mengen von Objekten instanziieren - interessant z. beim Parsen großer Dokumente in Objekte oder aus einer Datenbank.

Hier ist ein Vergleich der Erstellung von Objektbäumen mit einer Million Einträgen unter Verwendung von Slots und ohne Slots. Als Referenz auch die Performance bei der Verwendung von Klartexten 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, Teil 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
0
Red Pill