Hoe voorkom je dat klasgegevens worden gedeeld tussen instanties?

Wat ik wil is dit gedrag:

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]

Wat er natuurlijk gebeurt als ik print is:

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

Het is duidelijk dat ze de gegevens delen in klasse a. Hoe krijg ik afzonderlijke instanties om het gewenste gedrag te bereiken?


Antwoord 1, autoriteit 100%

Je wilt dit:

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

Het declareren van de variabelen in de klassendeclaratie maakt ze tot “klasse”-leden en niet tot instantieleden. Door ze binnen de __init__methode te declareren, zorgt u ervoor dat er een nieuwe instantie van de leden wordt gemaakt naast elke nieuwe instantie van het object, wat het gedrag is waarnaar u op zoek bent.


Antwoord 2, autoriteit 15%

Het geaccepteerde antwoord werkt, maar een beetje meer uitleg kan geen kwaad.

Klassekenmerken worden geen instantiekenmerken wanneer een instantie wordt gemaakt. Ze worden instantiekenmerken wanneer er een waarde aan wordt toegekend.

In de originele code wordt geen waarde toegekend aan het listattribuut na instantiëring; dus het blijft een klasse-attribuut. Het definiëren van een lijst binnen __init__werkt omdat __init__wordt aangeroepen na instantiatie. Als alternatief zou deze code ook de gewenste uitvoer produceren:

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

Het verwarrende scenario in de vraag zal echter nooit gebeuren met onveranderlijke objecten zoals getallen en strings, omdat hun waarde niet kan worden gewijzigd zonder toewijzing. Een code die bijvoorbeeld lijkt op het origineel met het type tekenreeks werkt probleemloos:

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

Dus om samen te vatten: klasse-attributen worden instantie-attributen als en alleen als er een waarde aan wordt toegekend na instantiatie, al dan niet in de __init__-methode. Dit is een goede zaak, want op deze manier kun je statische attributen hebben als je nooit een waarde toewijst aan een attribuut na instantiëring.


Antwoord 3, autoriteit 9%

Hoewel het geaccepteerde antwoord perfect is, zou ik graag een korte beschrijving willen toevoegen.

Laten we een kleine oefening doen

definieer allereerst een klasse als volgt:

class A:
    temp = 'Skyharbor'
    def __init__(self, x):
        self.x = x
    def change(self, y):
        self.temp = y

Dus wat hebben we hier?

  • We hebben een heel eenvoudige klasse die een attribuut tempheeft, wat een string is
  • Een __init__methode die self.x
  • instelt

  • Een wijzigingsmethode stelt self.temp
  • in

Behoorlijk rechttoe rechtaan tot nu toe, ja? Laten we nu beginnen te spelen met deze klasse. Laten we deze klasse eerst initialiseren:

a = A('Tesseract')

Doe nu het volgende:

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

Nou, a.tempwerkte zoals verwacht, maar hoe is de hel a.tempwerk? Nou, het werkte omdat Temp een klasse attribuut is. Alles in Python is een object. Hier is A ook een voorwerp van klasse type. Het attribuuttemp is dus een attribuut dat wordt vastgehouden door de Aklasse en als u de waarde van TEMP via A(en niet via een exemplaar van A), de gewijzigde waarde wordt weerspiegeld in het hele geval van Aklasse.
Laten we doorgaan en dat doen:

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

interessant is het niet? En Merk op dat id(a.temp)en id(a.temp)nog steeds hetzelfde zijn .

Elk Python-object wordt automatisch een __dict__kenmerk, die de lijst met kenmerken bevat. Laten we onderzoeken wat dit woordenboek bevat voor onze voorbeeldobjecten:

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

Merk op dat tempAttribuut wordt vermeld onder AKLASSE’S ATRIBUTEN, terwijl xwordt vermeld voor het exemplaar.

Dus hoe komt het dat we een gedefinieerde waarde van a.tempkrijgen als deze niet eens wordt vermeld voor het instantie A. Nou, dat is de magie van __getattribute__()-methode. In Python roept de gestippelde syntaxis automatisch deze methode aan, dus wanneer we schrijven a.tempvoert Python a.__getattribute__('temp'). Die methode voert de AttribuT-opzoekactie uit, d.w.z. vindt de waarde van het attribuut door op verschillende plaatsen te kijken.

De standaard implementatie van __getattribute__()zoekt eerst het interne woordenboek (dict ) van een object, dan het type van het object zelf. In dit geval a.__getattribute__('temp')Voert eerst uit a.__dict__['temp']en vervolgens a.__class__.__dict__['temp']

Oké, laten we onze changewijzigen:

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

Nu nu dat we hebben gebruikt self, print(a.temp)geeft ons een andere waarde van print(a.temp).

Nu als we het vergelijken met id(a.temp)en id(a.temp), zullen ze anders zijn.


Antwoord 4, Autoriteit 7%

U hebt “Lijst” als een “Class Level Property” en niet “External Property”. Om op het exemplaarniveau te beschikken, moet u ze initialiseren via verwijzingen met de parameter “ZELF” in de __init__-methode (of elders, afhankelijk van de situatie).

U hoeft niet strikt te initialiseren de instantieigenschappen in de __init__-methode, maar het zorgt voor eenvoudiger begrip.


Antwoord 5, Autoriteit 3%

Dus bijna elk antwoord lijkt hier een bepaald punt te missen. Klasse variabelen Nooit WORDT GEMEENSCHAPBAAR VARIABELEN zoals aangetoond door de onderstaande code. Door gebruik te maken van een metaclas om de variabele opdracht op het klasniveau te onderscheppen, kunnen we zien dat wanneer A.Myattr opnieuw wordt toegewezen, de Magic-methode van het veld Toewijzing op de klas niet wordt genoemd. Dit komt omdat de opdracht een nieuwe instantievariabele maakt. Dit gedrag heeft absoluut niets om te doen met de klassenvariabele zoals aangetoond door de tweede klasse die geen klassenvariabelen heeft en toch field-toewijzing mogelijk maakt.

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

KORT IN HET KORTKlassevariabelen hebben NIETS te maken met instantievariabelen.

LetterlijkerZe vallen toevallig in de buurt van zoekopdrachten op instanties. Klassevariabelen zijn in feite instantievariabelenop het klasseobject zelf. U kunt ook metaclass-variabelenhebben als u dat wilt, omdat metaclasses zelf ook objecten zijn. Alles is een object, of het nu wordt gebruikt om andere objecten te maken of niet, dus laat je niet verstrikken in de semantiek van het gebruik van de woordklasse in andere talen. In Python is een klasse eigenlijk gewoon een object dat wordt gebruikt om te bepalen hoe andere objecten kunnen worden gemaakt en wat hun gedrag zal zijn. Metaklassen zijn klassen die klassen maken, alleen om dit punt verder te illustreren.


Antwoord 6, autoriteit 2%

Ja, u moet declareren in de “constructor” als u wilt dat de lijst een objecteigenschap wordt en geen class-eigenschap.


Antwoord 7

Om uw variabele die door een andere instantie wordt gedeeld te beschermen, moet u elke keer dat u een instantie maakt een nieuwe instantievariabele maken. Wanneer u een variabele binnen een klasse declareert, is het een klassevariabele en wordt deze door alle instanties gedeeld. Als u het bijvoorbeeld verstandig wilt maken, moet u de methode initgebruiken om de variabele opnieuw te initialiseren, zoals verwijzen naar de instantie

Van Python Objects and Classdoor Programiz.com:

__init__()functie. Deze speciale functie wordt aangeroepen wanneer een nieuw object van die klasse wordt geïnstantieerd.

Dit type functie wordt ook wel constructors genoemd in Object Oriented
Programmeren (OOP). We gebruiken het normaal gesproken om alle variabelen te initialiseren.

Bijvoorbeeld:

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

Other episodes