Instanzvariablen vs. Klassenvariablen in Python

120

Ich habe Python-Klassen, von denen ich zur Laufzeit nur eine Instanz benötige. Es würde also ausreichen, die Attribute nur einmal pro Klasse und nicht pro Instanz zu haben. Wenn es mehr als eine Instanz geben würde (was nicht passieren wird), sollten alle Instanzen dieselbe Konfiguration haben. Ich frage mich, welche der folgenden Optionen besser oder "idiomatischer" Python wäre.

Klassenvariablen:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Instanzvariablen:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass
Deamon
quelle
4
Nachdem ich diese Frage gelesen und die Antwort gesehen hatte, war eine meiner ersten Fragen: "Wie greife ich auf Klassenvariablen zu?" --das liegt daran, dass ich bis jetzt nur Instanzvariablen verwendet habe. Als Antwort auf meine eigene Frage tun Sie dies über den Klassennamen selbst, obwohl Sie dies technisch auch über eine Instanz tun können. Hier ist ein Link zum Lesen für alle anderen mit der gleichen Frage: stackoverflow.com/a/3434596/4561887
Gabriel Staples

Antworten:

158

Wenn Sie ohnehin nur eine Instanz haben, ist es am besten, alle Variablen pro Instanz zu erstellen, einfach weil auf sie (ein wenig) schneller zugegriffen wird (eine Stufe weniger "Nachschlagen" aufgrund der "Vererbung" von Klasse zu Instanz). und es gibt keine Nachteile, die gegen diesen kleinen Vorteil abgewogen werden könnten.

Alex Martelli
quelle
7
Noch nie von dem Borg-Muster gehört? Nur eine Instanz zu haben, war der falsche Weg, sie überhaupt zu haben.
Devin Jeanpierre
434
@Devin, yep, ich habe von dem Borg-Muster gehört, da ich derjenige bin, der es eingeführt hat (2001, cfr code.activestate.com/recipes/… ;-) . Aber in einfachen Fällen ist nichts falsch daran, einfach eine einzelne Instanz ohne Durchsetzung zu haben.
Alex Martelli
2
@ user1767754, einfach, sie selbst zu erstellen python -mtimeit- aber nachdem ich dies gerade in Python3.4 getan habe, stelle ich fest, dass der Zugriff auf eine intKlassenvariable tatsächlich etwa 5 bis 11 Nanosekunden schneller ist als die Instanzvariable auf meiner alten Workstation - nicht sicher, was Codepath macht es so.
Alex Martelli
45

Weiteres Echo von Mike und Alex Ratschläge von und Hinzufügen meiner eigenen Farbe ...

Die Verwendung von Instanzattributen ist das typische ... das idiomatischere Python. Klassenattribute werden nicht so häufig verwendet, da ihre Anwendungsfälle spezifisch sind. Gleiches gilt für statische und Klassenmethoden im Vergleich zu "normalen" Methoden. Es handelt sich um spezielle Konstrukte, die sich mit bestimmten Anwendungsfällen befassen. Andernfalls handelt es sich um Code, der von einem abweichenden Programmierer erstellt wurde, der zeigen möchte, dass er eine dunkle Ecke der Python-Programmierung kennt.

Alex erwähnt in seiner Antwort, dass der Zugriff aufgrund einer geringeren Nachschlageebene (ein wenig) schneller sein wird. Lassen Sie mich diejenigen näher erläutern, die noch nicht wissen, wie dies funktioniert. Es ist dem variablen Zugriff sehr ähnlich - die Suchreihenfolge lautet:

  1. Einheimische
  2. Nichtlokale
  3. Globale
  4. eingebaute

Für den Attributzugriff lautet die Reihenfolge:

  1. Beispiel
  2. Klasse
  3. Basisklassen gemäß MRO (Method Resolution Order)

Beide Techniken arbeiten "von innen nach außen", dh die meisten lokalen Objekte werden zuerst überprüft, dann werden die äußeren Schichten nacheinander überprüft.

Angenommen, Sie suchen in Ihrem obigen Beispiel nach dem pathAttribut. Wenn es auf eine Referenz wie "self.path Python " , überprüft Python zuerst die Instanzattribute auf Übereinstimmung. Wenn dies fehlschlägt, wird die Klasse überprüft, aus der das Objekt instanziiert wurde. Schließlich werden die Basisklassen durchsucht. Wie Alex sagte, muss Ihr Attribut, wenn es in der Instanz gefunden wird, nicht woanders gesucht werden, daher sparen Sie ein wenig Zeit.

Wenn Sie jedoch auf Klassenattributen bestehen, benötigen Sie diese zusätzliche Suche. Oder Ihre andere Alternative besteht darin, über die Klasse anstelle der Instanz auf das Objekt zu verweisen, z. B. MyController.pathanstelle vonself.path . . Dies ist eine direkte Suche, die die verzögerte Suche umgeht. Wie Alex weiter unten erwähnt, handelt es sich jedoch um eine globale Variable. Sie verlieren also das Bit, von dem Sie dachten, dass Sie es speichern würden (es sei denn, Sie erstellen einen lokalen Verweis auf den [globalen] Klassennamen ).

Unter dem Strich sollten Sie die meiste Zeit Instanzattribute verwenden. Es wird jedoch Fälle geben, in denen ein Klassenattribut das richtige Werkzeug für den Job ist. Code, der beide gleichzeitig verwendet, erfordert die größte Sorgfalt, da bei Verwendung selfnur das Instanzattributobjekt und der Schattenzugriff auf das gleichnamige Klassenattribut verfügbar sind. In diesem Fall Sie müssen das Attribut durch die Klassennamen , um es zu verweisen zugreifen.

wescpy
quelle
@wescpy, MyControllerwird aber in den Globals nachgeschlagen, sodass die Gesamtkosten höher sind als self.pathbei patheiner Instanzvariablen (da die Methode == superschnelle Suche lokalself ist ).
Alex Martelli
Ah, stimmt. guter Fang. Ich denke, die einzige Problemumgehung besteht darin, eine lokale Referenz zu erstellen. An diesem Punkt lohnt es sich nicht wirklich.
wescpy
24

Im Zweifelsfall möchten Sie wahrscheinlich ein Instanzattribut.

Klassenattribute sind am besten für Sonderfälle reserviert, in denen sie sinnvoll sind. Der einzige sehr häufige Anwendungsfall sind Methoden. Es ist nicht ungewöhnlich , Klassenattribute für schreibgeschützte Konstanten zu verwenden, die Instanzen kennen müssen (obwohl der einzige Vorteil darin besteht, dass Sie auch von außerhalb der Klasse zugreifen möchten ), aber Sie sollten auf jeden Fall vorsichtig sein, wenn Sie einen Status in ihnen speichern , was selten ist, was Sie wollen. Selbst wenn Sie nur eine Instanz haben, sollten Sie die Klasse wie jede andere schreiben, was normalerweise die Verwendung von Instanzattributen bedeutet.

Mike Graham
quelle
1
Die Klassenvariablen sind eine Art schreibgeschützte Konstanten. Wenn Python mich Konstanten definieren lässt, hätte ich es als Konstanten geschrieben.
Deamon
1
@deamon, es ist etwas wahrscheinlicher, dass ich meine Konstanten vollständig außerhalb einer Klassendefinition setze und sie in Großbuchstaben benenne. Es ist auch in Ordnung, sie in die Klasse aufzunehmen. Das Erstellen von Instanzattributen schadet nichts, kann aber etwas seltsam sein. Ich denke nicht, dass dies ein Problem ist, bei dem die Community zu sehr hinter einer der Optionen steht.
Mike Graham
@MikeGraham FWIW, Googles Python Style Guide, schlägt vor, globale Variablen zugunsten von Klassenvariablen zu vermeiden. Es gibt jedoch Ausnahmen.
Dennis
Hier ist ein neuer Link zum Python Style Guide von Google . Jetzt wird einfach geschrieben: avoid global variablesund ihre Definition ist, dass globale Variablen auch Variablen sind, die als Klassenattribute deklariert werden. Pythons eigener Styleguide ( PEP-8 ) sollte jedoch der erste Ort sein, an dem Fragen dieser Art beantwortet werden. Dann sollte Ihr eigener Verstand das Werkzeug der Wahl sein (natürlich können Sie auch Ideen von Google erhalten).
Colidyre
4

Gleiche Frage bei Leistung beim Zugriff auf Klassenvariablen in Python - der hier von @Edward Loper angepasste Code

Lokale Variablen sind am schnellsten zugänglich, da sie weitgehend mit Modulvariablen verknüpft sind, gefolgt von Klassenvariablen, gefolgt von Instanzvariablen.

Es gibt 4 Bereiche, in denen Sie auf Variablen zugreifen können:

  1. Instanzvariablen (self.varname)
  2. Klassenvariablen (Classname.varname)
  3. Modulvariablen (VARNAME)
  4. Lokale Variablen (Variablenname)

Der Test:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

Das Ergebnis:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
robertcollier4
quelle