web-dev-qa-db-fra.com

Python Initialisation des membres de classe

Je viens de combattre un bug en Python. C'était l'un de ces bugs stupides pour les débutants, mais cela m'a fait penser aux mécanismes de Python (je suis un programmeur C++ de longue date, nouveau sur Python). Je vais exposer le code du buggy et expliquer ce que j'ai fait pour le réparer, puis j'ai quelques questions ...

Le scénario: J'ai une classe appelée A, qui a un membre de données de dictionnaire, voici son code (c'est une simplification bien sûr):

class A:
    dict1={}

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

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

La classe utilisant ce code est la classe 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 à do_something_with_aX() initialise une nouvelle instance "propre" de classe A et imprime le dictionnaire avant et après l'ajout.

Le bug (au cas où vous ne l'auriez pas encore compris):

>>> 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 commencent par le contenu de la dernière initialisation, etc. Je m'attendais à ce qu'ils commencent "frais".

Ce qui résout ce "bug", c'est évidemment d'ajouter:

self.dict1 = {}

Dans le constructeur __init__ De la classe A. Cependant, cela m'a fait me demander:

  1. Quelle est la signification de l'initialisation "dict1 = {}" au moment de la déclaration de dict1 (première ligne de la classe A)? C'est vide de sens?
  2. Quel est le mécanisme d'instanciation qui provoque la copie de la référence de la dernière initialisation?
  3. Si j'ajoute "self.dict1 = {}" dans le constructeur (ou tout autre membre de données), comment cela n'affecte-t-il pas le membre du dictionnaire des instances précédemment initialisées?

EDIT: Après les réponses, je comprends maintenant qu'en déclarant un membre de données et sans y faire référence dans le __init__ Ou ailleurs comme self.dict1, je définis pratiquement ce qui est appelé en C++/Java un membre de données statique . En l'appelant self.dict1, je le fais "lié à l'instance".

41
Roee Adler

Ce que vous continuez d'appeler un bug est le documenté , comportement standard des classes Python.

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

52
Paolo Bergantino

@Matthew: veuillez examiner la différence entre un membre de classe et un membre d'objet dans la programmation orientée objet. Ce problème se produit car la déclaration du dict original en fait un membre de la classe et non un membre d'objet (comme l'intention de l'affiche d'origine). Par conséquent, il existe une fois pour (est partagé entre) toutes les instances 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.

2
W.Prins

Lorsque vous accédez à l'attribut d'instance, par exemple, self.foo, python trouvera d'abord "foo" dans self.__dict__. S'il n'est pas trouvé, python trouvera 'foo' dans TheClass.__dict__

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

2
kcwu

Les déclarations de classe Pythons sont exécutées en tant que bloc de code et toutes les définitions de variables locales (dont les définitions de fonction sont un type spécial de) sont stockées dans l'instance de classe construite. En raison de la façon dont la recherche d'attributs fonctionne en Python, si un attribut n'est pas trouvé sur l'instance, la valeur de la classe est utilisée.

Le is n article intéressant sur la syntaxe des classes sur l'historique du blog Python.

0
Ants Aasma