1180 Stimmen

Verwendung von __slots__?

Was ist der Zweck von __slots__ in Python - vor allem im Hinblick darauf, wann ich es verwenden möchte und wann nicht?

1708voto

Russia Must Remove Putin Punkte 332216

Was ist in Python der Zweck von __slots__ und in welchen Fällen sollte man dies vermeiden?

TLDR:

Das besondere Attribut __slots__ ermöglicht es Ihnen, explizit anzugeben, welche Instanzattribute Sie von Ihren Objektinstanzen erwarten und welche Ergebnisse Sie erwarten:

  1. schneller Zugriff auf das Attribut.
  2. Platzersparnis in Erinnerung.

Die Platzersparnis ergibt sich aus

  1. Speichern von Wertreferenzen in Slots anstelle von __dict__ .
  2. Verweigerung __dict__ y __weakref__ Erstellung, wenn übergeordnete Klassen sie verweigern und Sie erklären __slots__ .

Kurze Warnungen

Kleiner Vorbehalt: 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 machen (das sollte es wahrscheinlich auch), Probleme treten sonst vielleicht nicht auf, aber Ihre Objekte nehmen mehr Platz ein, als sie eigentlich sollten. Python 3.8:

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)

Das liegt daran, dass der Slot-Deskriptor der Basis einen eigenen Slot hat, der von dem des Wrongs getrennt ist. Normalerweise sollte das nicht vorkommen, 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 gilt für die Mehrfachvererbung - mehrere "Elternklassen mit nicht leeren Slots" können nicht kombiniert werden.

Um dieser Einschränkung Rechnung zu tragen, sollten Sie bewährte Verfahren anwenden: Entfernen Sie alle Abstraktionen bis auf eine oder alle der Eltern, von denen ihre konkrete Klasse bzw. Ihre neue konkrete Klasse gemeinsam erben wird, und geben Sie der/den Abstraktion(en) leere Slots (genau wie abstrakte Basisklassen in der Standardbibliothek).

Siehe Abschnitt über Mehrfachvererbung unten für ein Beispiel.

Anforderungen:

  • Um Attribute zu haben, die in __slots__ tatsächlich in Slots gespeichert werden, anstatt in einem __dict__ muss eine Klasse erben von object (automatisch in Python 3, muss aber in Python 2 explizit angegeben werden).

  • Um die Schaffung eines __dict__ müssen Sie erben von object und alle Klassen in der Vererbung müssen deklarieren __slots__ und keiner von ihnen kann eine '__dict__' Eintrag.

Es gibt eine Menge Details, wenn Sie weiter lesen möchten.

Warum verwenden __slots__ : Schnellerer Zugriff auf Attribute.

Der Erfinder von Python, Guido van Rossum, Staaten die er tatsächlich geschaffen hat __slots__ für einen schnelleren Zugriff auf Attribute.

Es ist trivial, einen messbar deutlich 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

et

>>> 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 auf Windows habe ich gemessen, dass es etwa 15% schneller ist.

Warum verwenden __slots__ : Speicherplatzeinsparung

Ein weiterer Zweck der __slots__ ist es, den Speicherplatz, den jede Objektinstanz beansprucht, zu verringern.

In meinem eigenen Beitrag zur Dokumentation werden die Gründe dafür klar dargelegt :

Die Platzersparnis gegenüber der Verwendung von __dict__ können erheblich sein.

SQLAlchemy-Attribute eine Menge Speicherplatz zu sparen __slots__ .

Um dies zu überprüfen, verwenden Sie die Anaconda-Distribution von Python 2.7 auf Ubuntu Linux, mit guppy.hpy (auch bekannt als heapy) und sys.getsizeof die Größe einer Klasseninstanz ohne __slots__ deklariert wird, und nichts anderes, beträgt 64 Bytes. Das tut pas umfassen die __dict__ . Vielen Dank Python für die faule Bewertung wieder, die __dict__ wird anscheinend erst dann ins Leben gerufen, wenn sie referenziert wird, aber Klassen ohne Daten sind normalerweise nutzlos. Wenn sie ins Leben gerufen wird, wird die __dict__ Attribut mindestens 280 Byte zusätzlich beträgt.

Im Gegensatz dazu kann eine Klasseninstanz mit __slots__ erklärt zu sein () (keine Daten) beträgt nur 16 Byte, und 56 Gesamtbytes mit einem Element in Steckplätzen, 64 mit zwei.

Für 64-Bit-Python zeige ich den Speicherverbrauch in Bytes in Python 2.7 und 3.6, für __slots__ y __dict__ (keine Steckplätze definiert) für jeden Punkt, an dem das Diktat in 3.6 wächst (außer für die 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 der kleineren Dicts in Python 3 sehen wir also, wie schön __slots__ für Instanzen skalieren, um Speicherplatz zu sparen, und das ist ein wichtiger Grund, warum Sie die __slots__ .

Nur der Vollständigkeit halber sei darauf hingewiesen, dass pro Slot im Namensraum der Klasse einmalige Kosten von 64 Byte in Python 2 und 72 Byte in Python 3 anfallen, da Slots Datendeskriptoren wie Eigenschaften, sogenannte "Members", verwenden.

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

Demonstration von __slots__ :

Die Verweigerung der Schaffung eines __dict__ müssen Sie die Unterklasse object . Alles Unterklassen object in Python 3, aber in Python 2 mussten Sie explizit sein:

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 Folgendes definiert __slots__

class Child(Base):
    __slots__ = ('a',)

und jetzt:

c = Child()
c.a = 'a'

sondern:

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

Zu ermöglichen __dict__ Erstellung beim Unterklassifizieren von geschlitzten Objekten, fügen Sie einfach '__dict__' zum __slots__ (Beachten Sie, dass die Slots geordnet sind und Sie keine Slots wiederholen sollten, die bereits in übergeordneten Klassen enthalten sind):

class SlottedWithDict(Child): 
    __slots__ = ('__dict__', 'b')

swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'

et

>>> swd.__dict__
{'c': 'c'}

Oder Sie müssen sich nicht einmal anmelden __slots__ in Ihrer Unterklasse, und Sie werden weiterhin Slots von den Eltern verwenden, aber nicht die Erstellung einer __dict__ :

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

Und:

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

Allerdings, __slots__ kann bei Mehrfachvererbung zu Problemen führen:

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

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

Denn das Erstellen einer Kindklasse von Eltern mit beiden nicht leeren Slots schlägt fehl:

>>> 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 entfernen __slots__ von den Eltern, oder, wenn Sie die Kontrolle über die Eltern haben, geben Sie ihnen leere Slots, oder refactor to 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!

hinzufügen '__dict__' a __slots__ um eine dynamische Zuordnung zu erhalten:

class Foo(object):
    __slots__ = 'bar', 'baz', '__dict__'

und jetzt:

>>> foo = Foo()
>>> foo.boink = 'boink'

Also mit '__dict__' in Slots verlieren wir einige der Größenvorteile mit dem Vorteil einer dynamischen Zuweisung und haben immer noch Slots für die Namen, die wir erwarten.

Wenn Sie von einem Objekt erben, das nicht geschlitzt ist, erhalten Sie die gleiche Art von Semantik, wenn Sie __slots__ - Namen, die in __slots__ auf geschlitzte Werte verweisen, während alle anderen Werte in der Instanz __dict__ .

Vermeidung von __slots__ weil Sie in der Lage sein wollen, Attribute spontan hinzuzufügen, ist eigentlich kein guter Grund - fügen Sie einfach "__dict__" zu Ihrem __slots__ wenn dies erforderlich ist.

Auf ähnliche Weise können Sie auch __weakref__ a __slots__ ausdrücklich, wenn Sie diese Funktion benötigen.

Auf leeres Tupel setzen, wenn ein Namedupel untergeordnet wird:

Die namedtuple builtin machen unveränderliche Instanzen, die sehr leichtgewichtig sind (im Wesentlichen, die Größe der Tupel), aber um die Vorteile zu erhalten, müssen Sie es selbst tun, wenn Sie subclass sie:

from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
    """MyNT is an immutable and lightweight object"""
    __slots__ = ()

Verwendung:

>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'

Und der Versuch, ein unerwartetes Attribut zuzuweisen, führt zu einer AttributeError denn wir haben die Schaffung von __dict__ :

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

Sie kann ermöglichen __dict__ Erstellung durch Weglassen __slots__ = () aber Sie können keine nicht leeren __slots__ mit Subtypen von Tupel.

Größter Vorbehalt: Mehrfachvererbung

Selbst wenn die nicht leeren Slots für mehrere Elternteile 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 Elternteil scheint die größte Flexibilität zu bieten, dem Kind die Möglichkeit zu geben, zu verhindern oder zuzulassen, dass (durch Hinzufügen von '__dict__' um eine 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 haben keine haben Slots zu haben - wenn Sie sie also hinzufügen und später wieder entfernen, sollte das keine Probleme verursachen.

Ich lehne mich hier weit aus dem Fenster : Wenn Sie komponieren mixins oder mit abstrakte Basisklassen die nicht instanziiert werden sollen, eine leere __slots__ in diesen Elternteilen scheint im Hinblick auf die Flexibilität für Unterklassler der beste Weg zu sein.

Zur Veranschaulichung erstellen wir zunächst eine Klasse mit Code, den wir im Rahmen der 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 die oben genannten direkt verwenden, indem wir die erwarteten Slots erben und deklarieren:

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

Aber das ist uns egal, das ist triviale Einzelvererbung, wir brauchen eine andere Klasse, von der wir auch erben können, 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 nun beide Basen nicht leere Slots hätten, könnten wir das Folgende nicht tun. (Tatsächlich hätten wir, wenn wir wollten, Folgendes geben können AbstractBase nicht leere Slots a und b, und ließ sie in der untenstehenden Erklärung weg - sie drin zu lassen wäre falsch):

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

Und jetzt haben wir durch Mehrfachvererbung Funktionalität von beiden und können immer noch verweigern __dict__ y __weakref__ Instanziierung:

>>> 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, in denen Steckplätze zu vermeiden sind:

  • Vermeiden Sie sie, wenn Sie eine Leistung erbringen wollen __class__ Zuordnung zu einer anderen Klasse, die sie nicht hat (und man kann sie nicht hinzufügen), es sei denn, die Slot-Layouts sind identisch. (Ich bin sehr daran interessiert, zu erfahren, wer dies tut und warum).
  • Vermeiden Sie sie, wenn Sie Builtins mit variabler Länge wie long, tuple oder str unterklassifizieren und ihnen Attribute hinzufügen wollen.
  • Vermeiden Sie sie, wenn Sie darauf bestehen, Standardwerte über Klassenattribute für Instanzvariablen bereitzustellen.

Möglicherweise können Sie aus dem Rest des Dokuments weitere Vorbehalte herauslesen __slots__ Dokumentation (die 3.7-Entwicklungsdokumente sind die aktuellsten) zu dem ich in letzter Zeit wichtige Beiträge geleistet habe.

Kritiken an anderen Antworten

Die aktuellen Top-Antworten zitieren veraltete Informationen und sind ziemlich hanebüchen und verfehlen in einigen wichtigen Punkten das Ziel.

Verwenden Sie nicht "nur __slots__ bei der Instanziierung vieler Objekte"

Ich zitiere:

"Sie würden gerne die __slots__ wenn Sie viele (Hunderte, Tausende) Objekte der gleichen Klasse instanziieren werden.

Abstrakte Basisklassen, zum Beispiel aus dem collections Modul nicht instanziiert werden, aber __slots__ für sie erklärt werden.

Und warum?

Wenn ein Benutzer Folgendes ablehnen möchte __dict__ o __weakref__ Erstellung, dürfen diese Dinge in den übergeordneten Klassen nicht vorhanden sein.

__slots__ trägt zur Wiederverwendbarkeit bei der Erstellung von Schnittstellen oder Mixins bei.

Es ist wahr, dass viele Python-Benutzer nicht für die Wiederverwendbarkeit schreiben, aber wenn Sie es tun, ist die Option, unnötigen Platzverbrauch zu verweigern, wertvoll.

__slots__ macht das Beizen nicht kaputt

Wenn Sie ein geschlitztes Objekt beizen, kann es sein, dass es sich mit einer irreführenden Meldung beschwert TypeError :

>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled

Das ist eigentlich falsch. Diese Meldung kommt vom ältesten Protokoll, das die Standardeinstellung ist. Sie können das neueste Protokoll mit der Option -1 Argument. In Python 2.7 wäre dies 2 (das 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>

Ich würde dies also im Hinterkopf behalten, denn es ist ein gelöstes Problem.

Kritik an der (bis 2. Oktober 2016) akzeptierten Antwort

Der erste Absatz ist halb kurze Erklärung, halb Vorhersage. Hier ist der einzige Teil, der die Frage tatsächlich beantwortet

Die richtige Verwendung von __slots__ ist es, Platz in Objekten zu sparen. Anstelle eines dynamischen Diktats, das das Hinzufügen von Attributen zu Objekten zu jeder Zeit erlaubt, gibt es eine statische Struktur, die keine Ergänzungen nach der Erstellung zulässt. Dies spart den Overhead eines Diktats für jedes Objekt, das Slots verwendet

Die zweite Hälfte ist eine Wunschvorstellung, die nicht zutrifft:

Dies ist zwar manchmal eine nützliche Optimierung, wäre aber völlig unnötig, wenn der Python-Interpreter dynamisch genug wäre, um das dict nur dann zu benötigen, wenn es tatsächlich Ergänzungen zum Objekt gibt.

Python macht eigentlich etwas Ähnliches, nur dass es die __dict__ wenn auf sie zugegriffen wird, aber die Erstellung vieler Objekte ohne Daten ist ziemlich lächerlich.

Der zweite Absatz vereinfacht zu stark und geht an den tatsächlichen Gründen für die Vermeidung von __slots__ . Im Folgenden wird pas ein echter Grund, Zeitnischen zu vermeiden (für aktuell Gründe, siehe den Rest meiner Antwort oben):

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

Anschließend werden andere Möglichkeiten erörtert, wie dieses perverse Ziel mit Python erreicht werden kann, ohne dass dabei etwas mit __slots__ .

Der dritte Absatz ist eher eine Wunschvorstellung. Insgesamt handelt es sich größtenteils um abwegige Inhalte, die der Antwortende nicht einmal selbst verfasst hat, und trägt dazu bei, Kritikern der Website Munition zu liefern.

Nachweis der Speichernutzung

Erstellen Sie einige normale Objekte und geschlitzte Objekte:

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

Eine Million davon instanziieren:

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

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

Zugriff auf die regulären Objekte und ihre __dict__ und kontrollieren 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 Vereinheitlichung von Typen und Klassen in Python 2.2

Wenn Sie einen eingebauten Typ unterklassifizieren, wird automatisch zusätzlicher Platz zu den Instanzen hinzugefügt, um die folgenden Elemente unterzubringen __dict__ y __weakrefs__ . (Die __dict__ wird allerdings erst bei der Verwendung initialisiert, so dass Sie sich keine Gedanken über den Platz machen sollten, der durch ein leeres Wörterbuch für jede von Ihnen erstellte Instanz belegt wird). Wenn Sie diesen zusätzlichen Platz nicht benötigen, können Sie die Phrase " __slots__ = [] " zu Ihrer Klasse.

279voto

Jeff Bauer Punkte 13322

Zitat Jacob Hallen :

Die richtige Verwendung von __slots__ ist es, Platz in Objekten zu sparen. Anstatt ein eines dynamischen Diktats, das das Hinzufügen von Attributen zu Objekten jederzeit ermöglicht, gibt es eine statische Struktur, die keine Hinzufügungen nach der Erstellung zulässt. [Diese Verwendung von __slots__ eliminiert den Overhead eines Diktats für jedes Objekt]. Während 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 dass er das Diktat nur dann benötigt, wenn es tatsächlich Ergänzungen zum Objekt gibt.

Leider haben die Spielautomaten eine Nebenwirkung. Sie verändern das Verhalten von der Objekte, die Slots haben, auf eine Weise, die von Kontrollfreaks missbraucht werden kann und Static Typing Weenies missbraucht werden können. Das ist schlecht, denn die Kontrollfreaks sollten Metaklassen missbrauchen und die statischen Typisierungsfanatiker sollten die Dekoratoren verwenden, da es in Python nur einen offensichtlichen Weg geben sollte, etwas zu tun.

CPython intelligent genug machen, um Platz zu sparen, ohne __slots__ ist eine wichtige Das ist wahrscheinlich der Grund, warum es (noch) nicht auf der Liste der Änderungen für P3k steht.

143voto

Ryan Punkte 14482

Sie würden Folgendes verwenden wollen __slots__ wenn Sie viele (Hunderte, Tausende) Objekte derselben Klasse instanziieren wollen. __slots__ existiert nur als Werkzeug zur Speicheroptimierung.

Es wird dringend davon abgeraten, die __slots__ um die Erstellung von Attributen einzuschränken.

Beizen von Objekten mit __slots__ funktioniert nicht mit dem voreingestellten (ältesten) Pickle-Protokoll; es ist notwendig, eine neuere Version anzugeben.

Einige andere Introspektionsfunktionen von Python können ebenfalls beeinträchtigt werden.

74voto

Suraj Punkte 4649

Jedes Python-Objekt hat eine __dict__ atttribute, das ein Wörterbuch ist, das alle anderen Attribute enthält. z.B. wenn Sie eingeben self.attr python macht eigentlich self.__dict__['attr'] . Wie Sie sich vorstellen können, nimmt die Verwendung eines Wörterbuchs zum Speichern von Attributen zusätzlichen Platz und Zeit für den Zugriff darauf in Anspruch.

Wenn Sie jedoch __slots__ hat jedes Objekt, das für diese Klasse erstellt wird, keine __dict__ zuordnen. Stattdessen erfolgt der gesamte Zugriff auf die Attribute direkt über Zeiger.

Wenn Sie also eher eine Struktur im C-Stil als eine vollwertige Klasse wünschen, können Sie __slots__ zur Verdichtung der Größe der Objekte und zur Verringerung der Zugriffszeit auf die Attribute. Ein gutes Beispiel ist eine Point-Klasse mit den Attributen x und y. Wenn Sie viele Punkte haben werden, können Sie versuchen, mit __slots__ um etwas Speicherplatz zu sparen.

31voto

Evgeni Sergeev Punkte 20596

Ergänzend 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, zur Umsetzung __slots__ Es ist nur eine zusätzliche Zeile erforderlich (und Ihre Klasse muss eine New-Style-Klasse sein, falls sie es nicht schon ist). Auf diese Weise können Sie den Speicherbedarf dieser Klassen um das 5-fache reduzieren auf Kosten der Notwendigkeit, eigenen Pickle-Code zu schreiben, falls dies erforderlich sein sollte.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X