Wie kann vermieden werden, dass Klassendaten von Instanzen gemeinsam genutzt werden?

145

Was ich will, ist dieses Verhalten:

class a:
    list = []

x = a()
y = a()

x.list.append(1)
y.list.append(2)
x.list.append(3)
y.list.append(4)

print(x.list) # prints [1, 3]
print(y.list) # prints [2, 4]

Was beim Drucken wirklich passiert, ist natürlich:

print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]

Offensichtlich teilen sie die Daten in der Klasse a. Wie erhalte ich separate Instanzen, um das gewünschte Verhalten zu erzielen?

8steve8
quelle
14
Bitte nicht listals Attributname verwenden. listist eine integrierte Funktion zum Erstellen einer neuen Liste. Sie sollten Namensklassen mit Großbuchstaben schreiben.
Marc Tudurí

Antworten:

147

Du willst das:

class a:
    def __init__(self):
        self.list = []

Durch das Deklarieren der Variablen in der Klassendeklaration werden sie zu "Klassen" -Mitgliedern und nicht zu Instanzmitgliedern. Wenn Sie sie innerhalb der __init__Methode deklarieren, wird sichergestellt, dass neben jeder neuen Instanz des Objekts eine neue Instanz der Mitglieder erstellt wird. Dies ist das Verhalten, nach dem Sie suchen.

Abyx
quelle
5
Eine zusätzliche Klarstellung: Wenn Sie die List-Eigenschaft in einer der Instanzen neu zuweisen, hat dies keine Auswirkungen auf die anderen. Wenn Sie also so etwas getan haben x.list = [], können Sie es ändern und keine anderen beeinflussen. Das Problem , das Sie Gesicht ist , dass x.listund y.listdie gleiche Liste sind, also wenn Sie Append rufen auf einem, den anderen beeinflusst.
Matt Moriarity
Aber warum passiert das nur für Listen? Wenn ich eine Ganzzahl oder Zeichenfolge außerhalb des Init deklariert habe , wurde sie nicht von den Objekten gemeinsam genutzt? Kann jemand einen Dokumentlink zu diesem Konzept teilen?
Amal Ts
1
@AmalTs Es sieht so aus, als ob Sie nicht verstehen, wie die Zuweisung in Python funktioniert. Siehe dieses Video oder diesen SO-Beitrag . Das angezeigte Verhalten wird durch die Tatsache verursacht, dass Sie Listen mutieren, aber Verweise auf Ints und Strings neu binden.
25ндрей Беньковский
4
@AmalTs Hinweis: Es wird als schlechte Praxis angesehen, Klassenattribute als "faule" Standardwerte für Instanzattribute zu verwenden. Selbst wenn die Attribute unveränderlich sind, ist es besser, sie innerhalb zuzuweisen __init__.
25ндрей Беньковский
21

Die akzeptierte Antwort funktioniert, aber ein bisschen mehr Erklärung tut nicht weh.

Klassenattribute werden beim Erstellen einer Instanz nicht zu Instanzattributen. Sie werden zu Instanzattributen, wenn ihnen ein Wert zugewiesen wird.

Im ursprünglichen Code wird dem listAttribut nach der Instanziierung kein Wert zugewiesen . es bleibt also ein Klassenattribut. Das Definieren einer Liste innerhalb __init__funktioniert, da __init__es nach der Instanziierung aufgerufen wird. Alternativ würde dieser Code auch die gewünschte Ausgabe erzeugen:

>>> class a:
    list = []

>>> y = a()
>>> x = a()
>>> x.list = []
>>> y.list = []
>>> x.list.append(1)
>>> y.list.append(2)
>>> x.list.append(3)
>>> y.list.append(4)
>>> print(x.list)
[1, 3]
>>> print(y.list)
[2, 4]

Das verwirrende Szenario in der Frage wird jedoch niemals unveränderlichen Objekten wie Zahlen und Zeichenfolgen passieren, da deren Wert nicht ohne Zuweisung geändert werden kann. Beispielsweise funktioniert ein Code ähnlich dem Original mit dem Attributtyp Zeichenfolge problemlos:

>>> class a:
    string = ''


>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'

Zusammenfassend: Klassenattribute werden genau dann zu Instanzattributen, wenn ihnen nach der Instanziierung ein Wert zugewiesen wird, ob sie sich in der __init__Methode befinden oder nicht . Dies ist eine gute Sache, da Sie auf diese Weise statische Attribute haben können, wenn Sie einem Attribut nach der Instanziierung niemals einen Wert zuweisen.

jurgenreza
quelle
11

Sie haben "list" als "Eigenschaft auf Klassenebene" und nicht als "Eigenschaft auf Instanzebene" deklariert. Damit Eigenschaften auf Instanzebene festgelegt werden, müssen Sie sie durch Referenzieren mit dem Parameter "self" in der __init__Methode (oder je nach Situation an anderer Stelle) initialisieren .

Sie müssen die Instanzeigenschaften in der __init__Methode nicht unbedingt initialisieren , dies erleichtert jedoch das Verständnis.

jldupont
quelle
11

Obwohl die akzeptierte Antwort genau richtig ist, möchte ich eine kleine Beschreibung hinzufügen.

Machen wir eine kleine Übung

Definieren Sie zunächst eine Klasse wie folgt:

class A:
    temp = 'Skyharbor'

    def __init__(self, x):
        self.x = x

    def change(self, y):
        self.temp = y

Was haben wir hier?

  • Wir haben eine sehr einfache Klasse, die ein Attribut hat, tempdas eine Zeichenfolge ist
  • Eine __init__Methode, die setztself.x
  • Eine Änderungsmethode wird festgelegt self.temp

Bis jetzt ziemlich einfach, ja? Jetzt fangen wir an, mit dieser Klasse herumzuspielen. Initialisieren wir zuerst diese Klasse:

a = A('Tesseract')

Gehen Sie nun wie folgt vor:

>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor

Nun, a.temphat wie erwartet funktioniert, aber wie zum Teufel hat es A.tempfunktioniert? Nun, es hat funktioniert, weil Temp ein Klassenattribut ist. Alles in Python ist ein Objekt. Hier ist A auch ein Objekt der Klasse type. Daher ist das Attribut temp ein Attribut, das von der AKlasse gehalten wird. Wenn Sie den Wert von temp durch A(und nicht durch eine Instanz von a) ändern, wird der geänderte Wert in der gesamten Instanz der AKlasse wiedergegeben. Lassen Sie uns fortfahren und das tun:

>>> A.temp = 'Monuments'
>>> print(A.temp)
Monuments
>>> print(a.temp)
Monuments

Interessant, nicht wahr? Und beachten Sie, dass id(a.temp)und id(A.temp)sind immer noch die gleichen .

Jedes Python-Objekt erhält automatisch ein __dict__Attribut, das die Liste der Attribute enthält. Lassen Sie uns untersuchen, was dieses Wörterbuch für unsere Beispielobjekte enthält:

>>> print(A.__dict__)
{
    'change': <function change at 0x7f5e26fee6e0>,
    '__module__': '__main__',
    '__init__': <function __init__ at 0x7f5e26fee668>,
    'temp': 'Monuments',
    '__doc__': None
}
>>> print(a.__dict__)
{x: 'Tesseract'}

Beachten Sie, dass das tempAttribut unter Aden Attributen der Klasse aufgeführt ist, während xes für die Instanz aufgeführt ist.

Wie kommt es, dass wir einen definierten Wert erhalten , a.tempwenn es nicht einmal für die Instanz aufgeführt wird a. Nun, das ist die Magie von__getattribute__() Methode. In Python ruft die gepunktete Syntax diese Methode automatisch auf, sodass a.tempPython beim Schreiben ausgeführt wird a.__getattribute__('temp'). Diese Methode führt die Attributsuchaktion aus, dh sie ermittelt den Wert des Attributs, indem sie an verschiedenen Stellen sucht.

Die Standardimplementierung der __getattribute__()Suche durchsucht zunächst das interne Wörterbuch ( dikt ) eines Objekts und dann der Typ des Objekts selbst . In diesem Fall a.__getattribute__('temp')wird zuerst a.__dict__['temp']und dann ausgeführta.__class__.__dict__['temp']

Okay, jetzt lass uns unsere benutzen change Methode:

>>> a.change('Intervals')
>>> print(a.temp)
Intervals
>>> print(A.temp)
Monuments

Nun, da wir verwendet haben self , print(a.temp)gibt uns einen anderen Wert als print(A.temp).

Wenn wir nun id(a.temp)und vergleichen id(A.temp), werden sie unterschiedlich sein.

Swapnil
quelle
3

Ja, Sie müssen im "Konstruktor" deklarieren, wenn Sie möchten, dass die Liste eine Objekteigenschaft und keine Klasseneigenschaft wird.

Osanchezmon
quelle
3

Fast jede Antwort hier scheint einen bestimmten Punkt zu verfehlen. Klassenvariablen werden niemals zu Instanzvariablen, wie der folgende Code zeigt. Durch die Verwendung einer Metaklasse zum Abfangen der Variablenzuweisung auf Klassenebene können wir sehen, dass bei einer Neuzuweisung von a.myattr die magische Methode der Feldzuweisung für die Klasse nicht aufgerufen wird. Dies liegt daran, dass durch die Zuweisung eine neue Instanzvariable erstellt wird . Dieses Verhalten hat absolut nichts mit der Klassenvariablen zu tun, wie die zweite Klasse zeigt, die keine Klassenvariablen hat und dennoch eine Feldzuweisung zulässt.

class mymeta(type):
    def __init__(cls, name, bases, d):
        pass

    def __setattr__(cls, attr, value):
        print("setting " + attr)
        super(mymeta, cls).__setattr__(attr, value)

class myclass(object):
    __metaclass__ = mymeta
    myattr = []

a = myclass()
a.myattr = []           #NOTHING IS PRINTED
myclass.myattr = [5]    #change is printed here
b = myclass()
print(b.myattr)         #pass through lookup on the base class

class expando(object):
    pass

a = expando()
a.random = 5            #no class variable required
print(a.random)         #but it still works

ZUSAMENFASSEND Klassenvariablen haben NICHTS mit Instanzvariablen zu tun.

Genauer gesagt, sie befinden sich zufällig im Bereich der Suche nach Instanzen. Klassenvariablen sind tatsächlich Instanzvariablen im Klassenobjekt selbst. Sie können auch Metaklassenvariablen haben, wenn Sie möchten, da Metaklassen selbst ebenfalls Objekte sind. Alles ist ein Objekt, unabhängig davon, ob es zum Erstellen anderer Objekte verwendet wird oder nicht. Lassen Sie sich also nicht auf die Semantik der Verwendung der Wortklasse in anderen Sprachen ein. In Python ist eine Klasse eigentlich nur ein Objekt, mit dem bestimmt wird, wie andere Objekte erstellt werden und wie sie sich verhalten. Metaklassen sind Klassen, die Klassen erstellen, um diesen Punkt weiter zu veranschaulichen.

TheQabalist
quelle
0

Um Ihre von einer anderen Instanz gemeinsam genutzte Variable zu schützen, müssen Sie jedes Mal, wenn Sie eine Instanz erstellen, eine neue Instanzvariable erstellen. Wenn Sie eine Variable innerhalb einer Klasse deklarieren, ist dies eine Klassenvariable, die von allen Instanzen gemeinsam genutzt wird. Wenn Sie es zum Beispiel klug machen möchten, müssen Sie den Init verwenden Methode verwenden, um die Variable gemäß der Instanz neu zu initialisieren

Aus Python-Objekten und -Klassen von Programiz.com :

__init__()Funktion. Diese spezielle Funktion wird immer dann aufgerufen, wenn ein neues Objekt dieser Klasse instanziiert wird.

Diese Art von Funktion wird in der objektorientierten Programmierung (OOP) auch als Konstruktor bezeichnet. Wir verwenden es normalerweise, um alle Variablen zu initialisieren.

Beispielsweise:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
Projesh Bhoumik
quelle