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

146

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 est:

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

De toute évidence, ils partagent les données en classe a. Comment obtenir des instances distinctes pour obtenir le comportement que je souhaite?

8steve8
la source
14
Veuillez ne pas utiliser listcomme nom d'attribut. listest une fonction intégrée pour construire une nouvelle liste. Vous devez écrire les classes de noms avec une majuscule.
Marc Tudurí

Réponses:

147

Tu veux ça:

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

La déclaration des variables à l'intérieur de la déclaration de classe en fait des membres de "classe" et non des membres d'instance. Les déclarer dans la __init__méthode garantit qu'une nouvelle instance des membres est créée à côté de chaque nouvelle instance de l'objet, ce qui est le comportement que vous recherchez.

abyx
la source
5
Une précision supplémentaire: si vous deviez réaffecter la propriété de liste dans l'une des instances, cela n'affecterait pas les autres. Donc, si vous faisiez quelque chose comme x.list = [], vous pourriez alors le changer et n'affecter aucun autre. Le problème auquel vous êtes confronté est que x.listet que vous avez y.listla même liste, donc lorsque vous appelez append sur l'un, cela affecte l'autre.
Matt Moriarity
Mais pourquoi cela n'arrive-t-il que pour la liste? Lorsque j'ai déclaré un entier ou une chaîne en dehors de l' initialisation , il n'était pas partagé entre les objets? Quelqu'un peut-il partager un lien vers ce concept?
Amal Ts
1
@AmalTs Il semble que vous ne comprenez pas comment fonctionne l'affectation en python. Voir cette vidéo ou ce message SO . Le comportement que vous voyez est dû au fait que vous modifiez des listes mais que vous reliez des références à des entiers et des chaînes.
Андрей Беньковский
4
@AmalTs Remarque: il est considéré comme une mauvaise pratique d'utiliser des attributs de classe comme valeurs par défaut "paresseuses" pour les attributs d'instance. Même si les attributs sont d'un type immuable, il est préférable de les affecter à l'intérieur __init__.
Андрей Беньковский
21

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 attribuée.

Dans le code d'origine, aucune valeur n'est attribuée à l' listattribut après l'instanciation; il reste donc un attribut de classe. La définition de la liste à l'intérieur __init__fonctionne car elle __init__est appelée après l'instanciation. Alternativement, ce code produirait également la sortie souhaitée:

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

Cependant, le scénario déroutant de la question n'arrivera jamais aux objets immuables tels que les nombres et les 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 chaîne fonctionne sans aucun problème:

>>> class a:
    string = ''


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

Donc, pour résumer: les attributs de classe deviennent des attributs d'instance si et seulement si une valeur leur est assignée après instanciation, qu'ils soient dans la __init__méthode ou non . C'est une bonne chose car de cette façon, vous pouvez avoir des attributs statiques si vous n'attribuez jamais de valeur à un attribut après l'instanciation.

Jurgenreza
la source
11

Vous avez déclaré "liste" comme "propriété au niveau de la classe" et non comme "propriété au niveau de l'instance". Afin d'avoir des propriétés étendues au niveau de l'instance, vous devez les initialiser en les référençant avec le paramètre "self" dans la __init__méthode (ou ailleurs selon la situation).

Il n'est pas strictement nécessaire d'initialiser les propriétés de l'instance dans la __init__méthode, mais cela facilite la compréhension.

jldupont
la source
11

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'avons-nous ici?

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

Assez simple jusqu'à présent, oui? Maintenant, commençons à jouer avec cette classe. Initialisons d'abord cette classe:

a = A('Tesseract')

Maintenant, procédez comme suit:

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

Eh bien, a a.tempfonctionné comme prévu, mais comment diable a-t-il A.tempfonctionné? Eh bien, cela a fonctionné parce que temp est un attribut de classe. Tout en python est un objet. Ici A est aussi un objet de classe type. Ainsi, l'attribut temp est un attribut détenu par la Aclasse et si vous modifiez la valeur de temp via A(et non via une instance de a), la valeur modifiée sera reflétée dans toute l'instance de Aclasse. 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 .

Tout objet Python reçoit automatiquement un __dict__attribut, qui contient sa liste d'attributs. Examinons ce que contient ce dictionnaire 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' tempattribut est répertorié parmi Ales attributs de la classe tandis qu'il xest répertorié pour l'instance.

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

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

Bon maintenant, utilisons notre changeméthode:

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

Eh bien maintenant que nous l'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.

Swapnil
la source
3

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.

osanchezmon
la source
3

Donc presque toutes les réponses ici semblent manquer un point particulier. Les variables de classe ne deviennent jamais des variables d' instance comme le montre le code ci-dessous. En utilisant une métaclasse pour intercepter l'affectation de variable au niveau de la classe, nous pouvons voir que lorsqu'un.myattr est réaffecté, la méthode magique d'affectation de champ sur la classe n'est pas appelée. En effet, l'affectation crée une nouvelle variable d'instance . Ce comportement n'a absolument rien à voir avec la variable de classe comme démontré par la deuxième classe qui n'a pas de variables de classe et qui permet encore l'affectation de champ.

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 BREF Les variables de classe n'ont RIEN à voir avec les variables d'instance.

Plus clairement, ils se trouvent juste dans la portée des recherches sur les instances. Les variables de classe sont en fait des variables d'instance sur l'objet de classe lui-même. Vous pouvez également avoir des variables de métaclasse 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, donc ne soyez pas lié à la sémantique des autres langages d'utilisation de la classe de mots. En python, une classe n'est en réalité qu'un objet utilisé pour déterminer comment créer d'autres objets et quels seront leurs comportements. Les métaclasses sont des classes qui créent des classes, juste pour illustrer davantage ce point.

TheQabalist
la source
0

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 à l'intérieur d'une classe, c'est une variable de classe et partagée par toutes les instances. Si vous voulez le faire par exemple, vous devez utiliser la méthode init pour réinitialiser la variable en vous référant à l'instance

À partir des objets et de la classe 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 la programmation orientée objet (POO). 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
Projesh Bhoumik
la source