IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Mauvaise compréhension du partage d'attributs en héritage de classes [Python 2.X]


Sujet :

Python

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Novembre 2009
    Messages
    93
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2009
    Messages : 93
    Points : 54
    Points
    54
    Par défaut Mauvaise compréhension du partage d'attributs en héritage de classes
    Bonjour à tous,

    Dans la situation ci-dessous, l'attribut "test" de la classe mère ne devrait-il pas être initialisé à chaque nouvelle instance de Child ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    class Mother:
        test = []
     
        def get_test(self):
            self.test.append(1)
            return self.test
     
    class Child(Mother):
        """
        """
     
    for child in [Child(), Child(), Child(), Child(), Child()]
        print len(child.get_test())
    Au lieu de quoi, le résultat du test donne : 1, 2, 3, 4, 5, comme si toutes les instances de Child partageaient le même attribut "test".

    Cela me paraît contre-intuitif... Je fais erreur ?

    Comment puis-je faire en sorte que chaque instance de Child hérite de sa propre instance de Mother ?

    Merci pour vos lumières.

    --
    Python 2.7.3
    Ubuntu 12.04

  2. #2
    Invité
    Invité(e)
    Par défaut
    Bonjour,

    Oui, le langage Python considère la référence à l'objet initialisé dans la classdef et donc n'initialise pas un membre référençant des list() et des dict() pour chaque instance de la classe ou pour les instances de ses classes dérivées (peut-être par souci d'économie de mémoire, je ne sais pas).

    J'ai rencontré le même problème lorsque j'ai écrit la librairie tkRAD : https://github.com/tarball69/tkRAD/wiki/Accueil

    Pour résoudre le problème, il suffit de déclarer les types non natifs (autres que int, float, bool, str) dans le constructeur __init__().

    Exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
     
    class Mother:
     
        test = []
     
        def get_test (self):
     
            self.test.append(1)
     
            return self.test
     
        # end def
     
    # end class Mother
     
    class Child (Mother): pass
     
    # test nr. 1
     
    print("test #1:")
     
    for child in [Child(), Child(), Child(), Child(), Child()]:
     
        print(len(child.get_test()), child.test)
     
    # end for
     
     
    class Ancestor:
     
        def __init__ (self):
     
            # member inits
     
            self.test = []
     
        # end def
     
        def get_test (self):
     
            self.test.append(1)
     
            return self.test
     
        # end def
     
    # end class Ancestor
     
    class Inherit (Ancestor): pass
     
    # test nr. 2
     
    print("test #2:")
     
    for child in [Inherit(), Inherit(), Inherit(), Inherit(), Inherit()]:
     
        print(len(child.get_test()), child.test)
     
    # end for
    Là, ça fonctionne pour chaque instance de la classe ancêtre et pour ses classes dérivées.

    Les membres de classe dits de "structure" - déclarés dans la classdef au lieu de __init__() - n'ont qu'un intérêt assez limité dans la philosophie Python et devraient se limiter à des constantes type int, float, bool, str.

    @+.

  3. #3
    Membre expérimenté Avatar de plxpy
    Homme Profil pro
    Ingénieur géographe
    Inscrit en
    Janvier 2009
    Messages
    792
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur géographe
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Janvier 2009
    Messages : 792
    Points : 1 481
    Points
    1 481
    Par défaut
    Bonjour,

    Déjà, ce n'est pas un "problème" d'héritage :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    >>> class Mother:
        test = []
     
        def get_test(self):
            self.test.append(1)
            return self.test
     
    >>> for mother in [Mother()]*5:
    	print len(mother.get_test())
     
    1
    2
    3
    4
    5
    Deuxièmement, surtout quand on "titille" ce genre de choses, autant écrire proprement : sauf à être contraint et forcé d'utiliser une version antérieure à 2.5 (de mémoire), la classe Mother dérive de "object"

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class Mother(object):
       ...
    Je ne connais pas le comportement des classes "old style" et n'ai pas envie d'avoir des "effets de bord" inutiles.

    Enfin, ça m'a l'air d'être un bête problème attribut de classe VS attribut d'instance, non ?
    "La simplicité ne précède pas la complexité, elle la suit." - Alan J. Perlis
    DVP ? Pensez aux cours et tutos, ainsi qu'à la FAQ !

  4. #4
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Avril 2004
    Messages
    1 046
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 1 046
    Points : 1 376
    Points
    1 376
    Par défaut
    il y a un mécanisme de fallback (très utile je trouve) qui fait que si l'attribut d'objet n'est pas trouvé, il est pioché dans les attributs de class (qui est bien sûr commun aux objets).

    et il me semble que si on veut expliquer le comportement est différent de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    [Mother(),Mother(),Mother(),Mother(),Mother()]

  5. #5
    Membre expérimenté Avatar de plxpy
    Homme Profil pro
    Ingénieur géographe
    Inscrit en
    Janvier 2009
    Messages
    792
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur géographe
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Janvier 2009
    Messages : 792
    Points : 1 481
    Points
    1 481
    Par défaut
    Citation Envoyé par josmiley Voir le message
    et il me semble que si on veut expliquer le comportement est différent de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    [Mother(),Mother(),Mother(),Mother(),Mother()]
    Exact, ce n'est pas la même chose (ma fainéantise m'a fait faire une erreur de débutant !). Dans ce que j'ai écrit, on n'utilise toujours que la même instance de Mother, 5 fois de suite...

    Mais bon ...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    >>> class Mother:
    ...     test = []
    ...     def get_test(self):
    ...             self.test.append(1)
    ...             return self.test
    ... 
    >>> for mother in (Mother(), Mother(), Mother(), Mother(), Mother()):
    ...     print len(mother.get_test())
    ... 
    1
    2
    3
    4
    5
    "La simplicité ne précède pas la complexité, elle la suit." - Alan J. Perlis
    DVP ? Pensez aux cours et tutos, ainsi qu'à la FAQ !

  6. #6
    Membre du Club
    Profil pro
    Inscrit en
    Novembre 2009
    Messages
    93
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2009
    Messages : 93
    Points : 54
    Points
    54
    Par défaut
    Merci pour vos réponses, rapides et claires.
    J'avoue que je ne faisais la différence entre les structures natives et les dict/list.
    Mais je pense avoir pigé le raisonnement.

    "Pour l'honneur" :

    Pour un non-initié, c'est quand même contre-intuitif, comme si les objets étaient poreux entre eux si on n'ajoute pas une couche de vernis sur les bords

    Pour info, je viens de Java et suis arrivé sur Python par Django, et ce problème d'initialisation ne paraissait pas lors de l'utilisation des classes d'entités de bd, et où les attributs sont définis non en constructeur, mais en attribut "sous la classe" (sais pas comment dire).

    Il semble aussi que je sois passé à travers des nouvelles syntaxes de la 2.7. Dire que je dois me mettre à la 3+...

    "Et pour poursuivre" :

    J'avais effectivement ajouté un constructeur à la classe mère pour palier le "problème".
    MAIS je constatais en utilisant pdb.set_trace() qu'il n'était pas appelé lors de l'instanciation.

    Ce que je viens de réaliser, c'est que, comme j'étais en héritage multiple, il suffit que la première classe ait un constructeur pour que le constructeur de la seconde classe ne soit pas appelé.

    Traduction : dans ce cas :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Mother(object):
     
        def __init__(self):
            test = [] 
     
        def get_test(self):
            self.test.append(1)
            return self.test
     
    class Father(object):
        def __init__(self):
            pass
     
    class Child(Mother, Father):
        pass
     
    for child in [Child(), Child(), Child(), Child(), Child()]
        print len(child.get_test())
    on obtient bien 1, 1, 1, 1, 1.

    Mais avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class Child(Father, Mother):
        pass
    on retombe sur 1, 2, 3, 4, 5.

    J'ai résolu mon problème puisque pour l'instant, je peux intervertir les héritages (enfin, j'espère !).

    Mais pour bien faire les choses, ma question serait maintenant : en cas d'héritage multiple, comment invoquer l'ensemble des constructeurs des classes mères ? Va-t-il falloir jongler avec les super(Child, self).__init__(args, kwargs) ?

    --
    edit : Correction -> je viens de porter les modifications sur le code de développement, et non, mon problème persiste. Ou plutôt, il évolue : maintenant, c'est un jeu pas très net entre mes constructeurs et ceux de Django...
    edit 2 : ça y est, je m'en sors (j'avais ajouté un constructeur là où il valait mieux pas, i.e. dans une classe directement gérée par Django...).

  7. #7
    Membre éprouvé
    Homme Profil pro
    Aucune activité
    Inscrit en
    Novembre 2011
    Messages
    505
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Aucune activité

    Informations forums :
    Inscription : Novembre 2011
    Messages : 505
    Points : 926
    Points
    926
    Par défaut
    Citation Envoyé par Chello Voir le message
    Merci pour vos réponses, rapides et claires.
    J'avoue que je ne faisais la différence entre les structures natives et les dict/list.
    Mais je pense avoir pigé le raisonnement.

    "Pour l'honneur" :

    [INDENT]Pour un non-initié, c'est quand même contre-intuitif, comme si les objets étaient poreux entre eux si on n'ajoute pas une couche de vernis sur les bords

    Pour info, je viens de Java et suis arrivé sur Python par Django, et ce problème d'initialisation ne paraissait pas lors de l'utilisation des classes d'entités de bd, et où les attributs sont définis non en constructeur, mais en attribut "sous la classe" (sais pas comment dire).

    Il semble aussi que je sois passé à travers des nouvelles syntaxes de la 2.7. Dire que je dois me mettre à la 3+...
    Bonjour,
    Désolé de remonter le sujet, mais il ne faut bien préciser les choses parce que cela peut devenir un problème récurrent.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> lst = []
    >>> id(lst)
    19523224
    >>> lst.append(1)
    >>> id(lst)
    19523224
    >>> id(ch)
    12648288
    >>> ch += "b"
    >>> id(ch)
    19664640
    Ceci est la différence entre un "mutable" et un non mutable.
    Une liste est "mutable": la variable contient la référence et la modification de la variable ne modifie pas cette référence.
    Une chaîne ne l'est pas: la modification de la variable modifie aussi la référence. Pour modifier cette variable, python fait une copie intégrale de la précédente variable pour y appliquer la modification.

    C'est pour cela que:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    >>> def fct(a):
    	a.append(1)
     
     
    >>> lst = []
    >>> id(lst)
    20180624
    >>> fct(lst)
    >>> lst
    [1]
    >>> id(lst)
    20180624
    >>> def fct(ch):
    	ch += "b"
     
     
    >>> ch = "a"
    >>> id(ch)
    12648288
    >>> fct(ch)
    >>> id(ch)
    12648288
    >>> ch
    'a'
    >>>
    Dans la documentation python, c'est un point très important!!!

    C'est uniquement de cela que découle le "problème" des classes: un type "mutable" la classe peut être modifié par des instances de celle-ci. Au contraire d'un type non-mutable.

    En conséquence, à ce sujet là pour le moins, il n'y a aucune différence entre les diverses versions de Python!!!

    Hum... Désolé pour le déterrage!!
    Clodion

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Mauvaise compréhension de JOIN deux tables
    Par devjcc dans le forum Requêtes
    Réponses: 6
    Dernier message: 08/09/2010, 17h25
  2. Mauvaise compréhension de CssResource
    Par Eléonore23 dans le forum GWT et Vaadin
    Réponses: 5
    Dernier message: 26/07/2010, 17h00
  3. Réponses: 18
    Dernier message: 02/02/2010, 15h39
  4. Mauvaise compréhension de G_ASCII_DTOSTR_BUF_SIZE
    Par shazad dans le forum Débuter
    Réponses: 6
    Dernier message: 09/10/2009, 14h54
  5. mauvaise compréhension du "xor"
    Par dispa dans le forum Windows Forms
    Réponses: 1
    Dernier message: 06/11/2007, 09h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo