Wie soll ich Standardwerte für Instanzvariablen in Python deklarieren?

88

Soll ich meinen Klassenmitgliedern folgende Standardwerte geben:

class Foo:
    num = 1

oder so?

class Foo:
    def __init__(self):
        self.num = 1

In dieser Frage entdeckte ich, dass in beiden Fällen

bar = Foo()
bar.num += 1

ist eine genau definierte Operation.

Ich verstehe, dass die erste Methode mir eine Klassenvariable gibt, die zweite nicht. Wenn ich jedoch keine Klassenvariable benötige, sondern nur einen Standardwert für meine Instanzvariablen festlegen muss, sind beide Methoden gleich gut? Oder einer von ihnen "pythonischer" als der andere?

Eine Sache, die mir aufgefallen ist, ist, dass sie im Django-Tutorial die zweite Methode verwenden, um Modelle zu deklarieren. Persönlich denke ich, dass die zweite Methode eleganter ist, aber ich würde gerne wissen, was der "Standard" ist.

int3
quelle

Antworten:

131

Um die Antwort von bp zu erweitern, wollte ich Ihnen zeigen, was er mit unveränderlichen Typen meinte.

Erstens ist das in Ordnung:

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

Dies funktioniert jedoch nur für unveränderliche (unveränderliche) Typen. Wenn der Standardwert veränderbar wäre (was bedeutet, dass er ersetzt werden kann), würde dies stattdessen passieren:

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Beachten Sie, dass beide aund bein gemeinsames Attribut haben. Dies ist oft unerwünscht.

Dies ist die pythonische Methode zum Definieren von Standardwerten für Instanzvariablen, wenn der Typ veränderbar ist:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

Der Grund, warum mein erster Codeausschnitt funktioniert, ist, dass Python mit unveränderlichen Typen eine neue Instanz davon erstellt, wann immer Sie eine möchten. Wenn Sie 1 zu 1 hinzufügen müssen, erstellt Python eine neue 2 für Sie, da die alte 1 nicht geändert werden kann. Ich glaube, der Grund liegt hauptsächlich im Hashing.

Xavier Ho
quelle
2
Ich bin kürzlich auf diese viel einfachere Art gestoßen, Standardwerte für veränderbare Attribute zu implementieren. Schreiben Sie einfach self.attr = attr or []innerhalb der __init__Methode. Es erzielt das gleiche Ergebnis (glaube ich) und ist immer noch klar und lesbar.
Bill
4
Sorry Leute. Ich habe den Vorschlag in meinem obigen Kommentar genauer untersucht und anscheinend ist er nicht ganz derselbe und wird nicht empfohlen .
Bill
Warum hat die Klasse TestC die leeren Klammern? Ist das ein Python 2-Erbe?
ChrisG
55

Die beiden Schnipsel machen unterschiedliche Dinge, es ist also keine Frage des Geschmacks, sondern des richtigen Verhaltens in Ihrem Kontext. Die Python-Dokumentation erklärt den Unterschied, aber hier sind einige Beispiele:

Anlage A.

class Foo:
  def __init__(self):
    self.num = 1

Dies bindet numan die Foo- Instanzen . Änderungen an diesem Feld werden nicht an andere Instanzen weitergegeben.

So:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Anlage B.

class Bar:
  num = 1

Dies bindet numan die Bar- Klasse . Änderungen werden weitergegeben!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

Aktuelle Antwort

Wenn ich keine Klassenvariable benötige, sondern nur einen Standardwert für meine Instanzvariablen festlegen muss, sind beide Methoden gleich gut? Oder einer von ihnen "pythonischer" als der andere?

Der Code in Exponat B ist dafür eindeutig falsch: Warum sollten Sie ein Klassenattribut (Standardwert bei der Instanzerstellung) an die einzelne Instanz binden?

Der Code in Exponat A ist in Ordnung.

Wenn Sie in Ihrem Konstruktor Standardeinstellungen für beispielsweise Variablen angeben möchten, würde ich dies jedoch tun:

class Foo:
  def __init__(self, num = None):
    self.num = num if num is not None else 1

...oder auch:

class Foo:
  DEFAULT_NUM = 1
  def __init__(self, num = None):
    self.num = num if num is not None else DEFAULT_NUM

... oder sogar: (vorzuziehen, aber genau dann, wenn es sich um unveränderliche Typen handelt!)

class Foo:
  def __init__(self, num = 1):
    self.num = num

So können Sie vorgehen:

foo1 = Foo(4)
foo2 = Foo() #use default
badp
quelle
1
ist die in diesem Beispiel verwendete Init-Methode, die sich von der Unterstreichung___init__underscore für die Initialisierung @badp
Eliethesaiyan
Sollte nicht das erste Beispiel seindef __init__(self, num = None)
PyWalker2797
6

Die Verwendung von Klassenmitgliedern zur Angabe von Standardwerten funktioniert sehr gut, solange Sie darauf achten, dies nur mit unveränderlichen Werten zu tun. Wenn Sie versuchen, es mit einer Liste oder einem Diktat zu tun, wäre das ziemlich tödlich. Es funktioniert auch, wenn das Instanzattribut eine Referenz auf eine Klasse ist, solange der Standardwert None ist.

Ich habe gesehen, dass diese Technik sehr erfolgreich in Repoze verwendet wurde, einem Framework, das auf Zope ausgeführt wird. Der Vorteil hierbei ist nicht nur, dass beim Beibehalten Ihrer Klasse in der Datenbank nur die nicht standardmäßigen Attribute gespeichert werden müssen, sondern auch, wenn Sie dem Schema ein neues Feld hinzufügen müssen, dass alle vorhandenen Objekte das neue Feld mit seinem Standardfeld sehen Wert, ohne dass die gespeicherten Daten tatsächlich geändert werden müssen.

Ich finde, dass es auch in der allgemeineren Codierung gut funktioniert, aber es ist eine Stilsache. Verwenden Sie alles, womit Sie am glücklichsten sind.

Duncan
quelle
3

Die Verwendung von Klassenmitgliedern für Standardwerte von Instanzvariablen ist keine gute Idee, und es ist das erste Mal, dass ich diese Idee überhaupt erwähnt habe. In Ihrem Beispiel funktioniert es, aber in vielen Fällen kann es fehlschlagen. Wenn der Wert beispielsweise veränderbar ist, wird durch Ändern in einer nicht geänderten Instanz der Standardwert geändert:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]
Max Shawabkeh
quelle
außer dass nur der allererste []dort geklont wurde; x.lund y.lsind sowohl böse Namen als auch verschiedene unmittelbare Werte, die mit demselben Referenten verbunden sind. (Sprachanwalt Begriffe
erfunden
1

Sie können Klassenvariablen auch als Keine deklarieren, wodurch die Weitergabe verhindert wird. Dies ist nützlich, wenn Sie eine genau definierte Klasse benötigen und AttributeErrors verhindern möchten. Beispielsweise:

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

Auch wenn Sie Standardeinstellungen benötigen:

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

Befolgen Sie natürlich weiterhin die Informationen in den anderen Antworten zur Verwendung von veränderlichen und unveränderlichen Typen als Standard in __init__.

Realistisch
quelle
0

Mit Datenklassen , einer in Python 3.7 hinzugefügten Funktion, gibt es jetzt eine weitere (recht bequeme) Möglichkeit, Standardwerte für Klasseninstanzen festzulegen. Der Dekorator dataclassgeneriert automatisch einige Methoden für Ihre Klasse, z. B. den Konstruktor. In der oben verlinkten Dokumentation heißt es: "Die in diesen generierten Methoden zu verwendenden Mitgliedsvariablen werden mithilfe von Anmerkungen vom Typ PEP 526 definiert ."

In Anbetracht des Beispiels von OP könnten wir es folgendermaßen implementieren:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

Beim Erstellen eines Objekts vom Typ dieser Klasse können wir den Wert optional überschreiben.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
Johannes Gehrs
quelle