42 votes

Initialisation des membres de la classe Python

J'ai tout récemment lutté contre un bug dans Python. C'était une de ces stupides newbie de bugs, mais il m'a fait réfléchir sur les mécanismes de Python (je suis longtemps programmeur C++, Python). Je mettrai sur le code bogué et d'expliquer ce que j'ai fait pour le résoudre, et puis j'ai quelques questions...

Le scénario: j'ai une classe appelée, qui dispose d'un dictionnaire de données des membres, voici son code (c'est la simplification de cours):

class A:
    dict1={}

    def add_stuff_to_1(self, k, v):
    	self.dict1[k]=v

    def print_stuff(self):
    	print(self.dict1)

La classe à l'aide de ce code est de catégorie B:

class B:

    def do_something_with_a1(self):
    	a_instance = A()
    	a_instance.print_stuff()  		
    	a_instance.add_stuff_to_1('a', 1)
    	a_instance.add_stuff_to_1('b', 2)    
    	a_instance.print_stuff()

    def do_something_with_a2(self):
    	a_instance = A()    
    	a_instance.print_stuff()    		
    	a_instance.add_stuff_to_1('c', 1)
    	a_instance.add_stuff_to_1('d', 2)    
    	a_instance.print_stuff()

    def do_something_with_a3(self):
    	a_instance = A()    
    	a_instance.print_stuff()    		
    	a_instance.add_stuff_to_1('e', 1)
    	a_instance.add_stuff_to_1('f', 2)    
    	a_instance.print_stuff()

    def __init__(self):
    	self.do_something_with_a1()
    	print("---")
    	self.do_something_with_a2()
    	print("---")
    	self.do_something_with_a3()

Notez que chaque appel d' do_something_with_aX() initialise une nouvelle "propre" de l'instance de la classe A, et imprime le dictionnaire avant et après l'ajout.

Le bug (dans le cas où vous n'avez pas compris encore):

>>> b_instance = B()
{}
{'a': 1, 'b': 2}
---
{'a': 1, 'b': 2}
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
---
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
{'a': 1, 'c': 1, 'b': 2, 'e': 1, 'd': 2, 'f': 2}

Dans la deuxième initialisation de la classe A, les dictionnaires ne sont pas vides, mais commencer avec le contenu de la dernière initialisation, et ainsi de suite. Je m'attendais à démarrer "frais".

Ce qui permet de résoudre ce "bug" est évidemment d'ajouter:

self.dict1 = {}

Dans l' __init__ constructeur de la classe A. Cependant, qui m'a fait me demander:

  1. Quel est le sens de la "dict1 = {}" initialisation au point de dict1 de la déclaration (première ligne en classe? Il est dénuée de sens?
  2. Quel est le mécanisme d'instanciation qui provoque la copie de la référence à partir de la dernière initialisation?
  3. Si j'ajoute "l'auto.dict1 = {}" dans le constructeur (ou tout autre membre de données), comment est-il pas d'incidence sur le dictionnaire membre de déjà initialisé instances?


EDIT: Suivant les réponses que je comprends maintenant que, par la déclaration d'un membre de données et de ne pas s'y référant dans l' __init__ ou quelque part d'autre que soi.dict1, je suis pratiquement la définition de ce qu'on appelle en C++/Java une donnée membre statique. En appelant à soi-même.dict1 je fais "instance"sédentaires".

53voto

Paolo Bergantino Points 199336

Ce que vous continuez à appeler un bogue est le comportement standard et documenté des classes Python.

Déclarer un dict en dehors de __init__ comme vous l'avez fait initialement, c'est déclarer une variable au niveau de la classe. Il n'est créé qu'une fois au début, chaque fois que vous créez de nouveaux objets, il réutilise ce même dict. Pour créer des variables d'instance, vous les déclarez avec self en __init__ ; c'est aussi simple que ça.

2voto

W.Prins Points 138

@ Matthew: Vérifiez la différence entre un membre du groupe et un membre de l'objet dans la programmation orientée objet. Ce problème est dû à la déclaration du dict d'origine qui en fait un membre de la classe et non un membre de l'objet (contrairement à l'intention de l'affiche d'origine). Par conséquent, il existe une fois pour toutes les occurrences de la classe (c'est-à-dire une fois). pour la classe elle-même, en tant que membre de l'objet classe lui-même), le comportement est donc parfaitement correct.

2voto

kcwu Points 2687

Lorsque vous accédez à l'attribut d'instance, par exemple self.foo, python recherchera d'abord 'foo' dans self.__dict__ . Si non trouvé, python trouvera 'foo' dans TheClass.__dict__

Dans votre cas, dict1 est de classe A, pas d'instance.

0voto

Ants Aasma Points 22921

Pythons déclarations de classe sont exécutés comme un bloc de code local et de définitions de variables (dont les définitions de fonctions sont un type spécial d') sont stockées dans la construction d'instance de classe. En raison de la façon dont l'attribut de rechercher des œuvres en Python, si un attribut n'est pas trouvé sur l'instance de la valeur de la classe est utilisé.

La est un article intéressant à propos de la classe de syntaxe sur l'histoire de Python blog.

0voto

too much php Points 27983

Si c'est votre code:

 class ClassA:
    dict1 = {}
a = ClassA()
 

Alors vous vous attendiez probablement à ce que cela se produise en Python:

 class ClassA:
    __defaults__['dict1'] = {}

a = instance(ClassA)
# a bit of pseudo-code here:
for name, value in ClassA.__defaults__:
    a.<name> = value
 

Pour autant que je peux dire, qui est ce qui se passe, sauf que dict a son pointeur copié, au lieu de la valeur, ce qui est le comportement par défaut partout en Python. Regardez ce code:

 a = {}
b = a
a['foo'] = 'bar'
print b
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X