web-dev-qa-db-fra.com

Comment éviter d'avoir des données de classe partagées entre les instances?

Ce que je veux, c'est ce comportement:

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]

Bien sûr, ce qui se passe réellement lorsque j'imprime, c'est:

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

Clairement, ils partagent les données dans la classe a. Comment obtenir des instances distinctes pour obtenir le comportement souhaité?

116
8steve8

Tu veux ça:

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

Déclarer les variables à l'intérieur de la déclaration de classe en fait des membres de "classe" et non des membres d'instance. Leur déclaration dans la méthode __init__ permet de s'assurer qu'une nouvelle instance des membres est créée à côté de chaque nouvelle instance de l'objet, ce qui correspond au comportement que vous recherchez.

117
abyx

La réponse acceptée fonctionne mais un peu plus d'explication ne fait pas de mal. 

Les attributs de classe ne deviennent pas des attributs d'instance lorsqu'une instance est créée. Ils deviennent des attributs d'instance lorsqu'une valeur leur est affectée.

Dans le code d'origine, aucune valeur n'est attribuée à l'attribut list après l'instanciation; il reste donc un attribut de classe. La définition de la liste dans __init__ fonctionne car __init__ est appelé après l'instanciation. Alternativement, ce code produirait également le résultat souhaité:

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

Toutefois, le scénario confus de la question ne se produira jamais pour des objets immuables tels que des nombres et des chaînes, car leur valeur ne peut pas être modifiée sans affectation. Par exemple, un code similaire à l'original avec le type d'attribut string fonctionne sans problème:

>>> class a:
    string = ''


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

En résumé, les attributs class deviennent des attributs d'instance si et seulement si une valeur leur est affectée après l'instanciation, qu'elle soit dans la méthode __init__ ou non. C'est une bonne chose car vous pouvez ainsi avoir des attributs statiques si vous n'attribuez jamais de valeur à un attribut après l'instanciation.

13
jurgenreza

Vous avez déclaré "liste" en tant que "propriété de niveau classe" et non pas "propriété de niveau instance". Pour que les propriétés soient étendues au niveau de l'instance, vous devez les initialiser en les référençant avec le paramètre "self" dans la méthode __init__ (ou ailleurs, selon la situation).

Vous n'êtes pas obligé d'initialiser les propriétés de l'instance dans la méthode __init__, mais cela facilite la compréhension.

10
jldupont

Bien que la réponse acceptée soit parfaite, je voudrais ajouter une petite description.

Faisons un petit exercice 

tout d'abord définir une classe comme suit:

class A:
    temp = 'Skyharbor'

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

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

Alors qu'est-ce qu'on a ici?

  • Nous avons une classe très simple qui a un attribut temp qui est une chaîne
  • Une méthode __init__ qui définit self.x 
  • Une méthode de changement définit self.temp

Assez simple jusqu'ici oui? Commençons maintenant à jouer avec cette classe. Initialisons d'abord cette classe:

a = A('Tesseract')

Maintenant, faites ce qui suit:

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

Bien, a.temp a fonctionné comme prévu, mais comment diable A.temp a-t-il fonctionné? Cela a bien fonctionné car temp est un attribut de classe. Tout en python est un objet. Ici, A est également un objet de la classe type. Ainsi, l'attribut temp est un attribut détenu par la classe A et si vous modifiez la valeur de temp par A (et non par une instance de a), la valeur modifiée sera reflétée dans toutes les instances de A class .Allons-y et faisons cela:

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

Intéressant n'est-ce pas? Et notez que id(a.temp) et id(A.temp) sont toujours les mêmes .

Chaque objet Python se voit automatiquement attribuer un attribut __dict__, qui contient sa liste d'attributs. Examinons ce que ce dictionnaire contient pour nos exemples d'objets:

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

Notez que l'attribut temp est répertorié parmi les attributs de la classe A tandis que x est répertorié pour l'instance.

Alors, comment se fait-il que nous obtenions une valeur définie de a.temp si elle n’est même pas répertoriée pour l’instance a. C'est la magie de la méthode __getattribute__(). En Python, la syntaxe en pointillé appelle automatiquement cette méthode. Ainsi, lorsque nous écrivons a.temp, Python exécute a.__getattribute__('temp'). Cette méthode effectue l’action de recherche d’attributs, c’est-à-dire trouve la valeur de l’attribut en cherchant à différents endroits.

L'implémentation standard de __getattribute__() cherche d'abord le dictionnaire interne ( dict ) d'un objet, puis le type de l'objet lui-même. Dans ce cas, a.__getattribute__('temp') exécute d'abord a.__dict__['temp'] et ensuite a.__class__.__dict__['temp']

Bon maintenant, utilisons notre méthode change:

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

Eh bien, maintenant que nous avons utilisé self, print(a.temp) nous donne une valeur différente de print(A.temp)

Maintenant, si nous comparons id(a.temp) et id(A.temp), ils seront différents.

7
Swapnil

Donc, presque chaque réponse semble manquer un point particulier. Les variables de classe jamais deviennent des variables d'instance, comme l'illustre le code ci-dessous. En utilisant une métaclasse pour intercepter une affectation de variable au niveau de la classe, nous pouvons voir que, lorsque a.myattr est réaffecté, la méthode magique d’affectation de champs de la classe n’est pas appelée. En effet, l’affectation crée une nouvelle variable d’instance _. Ce comportement a absolument rien à faire avec la variable de classe, comme le montre la deuxième classe qui n'a pas de variables de classe et permet néanmoins l'affectation de champs.

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

EN COURT Les variables de classe n'ont rien à voir avec les variables d'instance.

_ {Plus clairement Ils se trouvent juste dans le champ des recherches sur les instances. Les variables de classe sont en fait variables d'instance sur l'objet de classe lui-même. Vous pouvez également avoir metaclass variables si vous le souhaitez, car les métaclasses elles-mêmes sont également des objets. Tout est un objet, qu’il soit utilisé pour créer d’autres objets ou non, aussi ne soyez pas lié à la sémantique de l’utilisation des autres langages de la classe Word. En python, une classe n'est en réalité qu'un objet utilisé pour déterminer comment créer d'autres objets et définir leur comportement. Les métaclasses sont des classes qui créent des classes, simplement pour illustrer ce point. 

3
TheQabalist

Oui, vous devez déclarer dans le "constructeur" si vous voulez que la liste devienne une propriété d'objet et non une propriété de classe.

3
osanchezmon

Pour protéger votre variable partagée par une autre instance, vous devez créer une nouvelle variable d'instance chaque fois que vous créez une instance. Lorsque vous déclarez une variable dans une classe, c'est une variable de classe partagée par toutes les instances. Si vous voulez le définir par exemple, vous devez utiliser la méthode init pour réinitialiser la variable en vous référant à l’instance. 

De objets et classes Python de Programiz.com :

__init__() fonction. Cette fonction spéciale est appelée chaque fois qu'un nouvel objet de cette classe est instancié.

Ce type de fonction est également appelé constructeur dans Object Oriented Programmation (OOP). Nous l'utilisons normalement pour initialiser toutes les variables.

Par exemple:

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