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 :

Héritage multiple et super()


Sujet :

Python

  1. #1
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 721
    Points : 31 044
    Points
    31 044
    Billets dans le blog
    1
    Par défaut Héritage multiple et super()
    Bonjour à tous

    Jusqu'à présent, j'étais persuadé que super() savait gérer l'héritage multiple.

    En effet, sur cet exemple...
    Code python : 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
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
     
    class A:
    	def __init__(self):
    		print("A.init")
     
    class B(A):
    	def __init__(self):
    		super().__init__()
    		print("B.init")
     
    class C(A):
    	def __init__(self):
    		super().__init__()
    		print("C.init")
     
    class D(B, C):
    	def __init__(self):
    		super().__init__()
    		print("D.init")
     
    xxx=D()
    ... le résultat est le suivant
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A.init
    C.init
    B.init
    D.init
    Cela montre qu'à l'instanciation de D, son super() appelle le init de C et de B dont il hérite (l'ordre des appels étant défini par le MRO) donc l'héritage multiple est respecté.

    Mais si on modifie le code ainsi...
    Code python : 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
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
     
    class B:
    	def __init__(self):
    		print("B.init")
     
    class C:
    	def __init__(self):
    		print("C.init")
     
    class D(B, C):
    	def __init__(self):
    		super().__init__()
    		print("D.init")
     
    xxx=D()
    ... cela donne le résultat suivant
    A partir de là, je m'interroge. Pourquoi le super() de D n'a-t-il pas appelé le init de C dont il hérite (alors que c'était bien le cas dans l'exemple précédent) ?

    Si maintenant je re-modifie le code en rajoutant un super() dans tous les objets (y compris ceux de base)...
    Code python : 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
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
     
    class B:
    	def __init__(self):
    		super().__init__()
    		print("B.init")
     
    class C:
    	def __init__(self):
    		super().__init__()
    		print("C.init")
     
    class D(B, C):
    	def __init__(self):
    		super().__init__()
    		print("D.init")
     
    xxx=D()
    ... j'obtiens un résultat plus en adéquation avec mon attente (et avec le premier code)...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    C.init
    B.init
    D.init
    ... ce qui correspond plus à mon attente mais ouvre une nouvelle question: faut-il impérativement écrire un super() dans tous ses objets pour qu'on puisse en hériter correctement y compris en héritage multiple?

    Merci à tous de votre intérêt.

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 319
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 319
    Points : 36 829
    Points
    36 829
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    ... ce qui correspond plus à mon attente mais ouvre une nouvelle question: faut-il impérativement écrire un super() dans tous ses objets pour qu'on puisse en hériter correctement y compris en héritage multiple?
    oui et non.
    Le "non" est que le MRO sert à ordonner la recherche dans l'arbre d'héritage lorsqu'on cherche un attribut.
    Exemple A définit toto, B hérite de A sans surcharger toto => B().toto() ira récupérer l'objet toto dans A et l'exécute <point>.

    Si toto surcharge la méthode définie par un ancêtre, il devra utiliser super pour y accéder via le MRO et l'exécuter a son tour.
    Donc oui, il faut ajouter super pour que le suivant soit exécuté.

    Maintenant, si on a A et B et C qui hérite de A et de B... lorsqu'on écrit A on ne pensera pas forcément à ajouter le "super" pour qu'on puisse fabriquer le C qui... Pire, __init__ existe pour tous les objets... mais pas les méthodes utilisateurs (le toto): ajouter super().toto() dans A.toto suppose que B.toto existe (sinon ça plante) et quelques précautions à prendre.

    - W

  3. #3
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 478
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 478
    Points : 9 278
    Points
    9 278
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    A tort ou à raison, je n'utilise pas "super" pour les héritages multiples, mais j'initialise séparément tous les ancêtres:

    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
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
     
    class B:
        def __init__(self):
            self.xb = "B"
            print("B.init")
     
    class C:
        def __init__(self):
            self.xc = "C"
            print("C.init")
     
    class D(B, C):
        def __init__(self):
            B.__init__(self)
            C.__init__(self)
            self.xd = "D"
            print("D.init")
     
    xxx = D()
     
    print(xxx.xc, xxx.xb, xxx.xd)
    Résultat:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    B.init
    C.init
    D.init
    C B D
    Jusqu'à présent, je n'ai pas rencontré de problème avec cette méthode. Mais peut-être y a-t-il une solution avec "super"?

    [edit] grillé par Sve@r

  4. #4
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 721
    Points : 31 044
    Points
    31 044
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par wiztricks Voir le message
    oui et non.
    Merci de ta réponse

    Citation Envoyé par wiztricks Voir le message
    Pire, __init__ existe pour tous les objets... mais pas les méthodes utilisateurs (le toto): ajouter super().toto() dans A.toto suppose que B.toto existe (sinon ça plante)
    Exact

    Code python : 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
    class B(object):
    	def __init__(self):
    		super().__init__()
    		print("B.init")
    	def action(self):
    		#super().action()
    		print("B.action")
     
    class C(object):
    	def __init__(self):
    		super().__init__()
    		print("C.init")
    	def action(self):
    		#super().action()
    		print("C.action")
     
    class D(B, C):
    	def __init__(self):
    		super().__init__()
    		print("D.init")
    	def action(self):
    		super().action()
    		print("D.action")
     
    xxx=D()
    xxx.action()

    Si on décommente les super().action() ça plante. Mais si on les commente (ou on ne les met pas) je n'obtiens que
    Et il me manque mon C.action !!!

    Donc mettre super() partout n'est pas la solution (d'ailleurs en réalité il n'y est pas dans les objets des autres car j'ai ouvert ce topic suite à ce post où je fais un héritage multiple qui ne se fait pas)

    Donc pour en revenir au __init__ de ma classe D, si je veux réellement hériter de B et C, je ne peux pas passer par super(). Je suis obligé d'écrire explicitement
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class D(B, C):
    	def __init__(self):
    		B.__init__(self)
    		C.__init__(self)
    		print("D.init")
    ???
    Finalement super() ce n'est pas si super() que ça... (ouais elle était facile !!!)

  5. #5
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 478
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 478
    Points : 9 278
    Points
    9 278
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    Donc mettre super() partout n'est pas la solution (d'ailleurs en réalité il n'y est pas dans les objets des autres car j'ai ouvert ce topic suite à ce post où je fais un héritage multiple qui ne se fait pas)

    Donc pour en revenir au __init__ de ma classe D, si je veux réellement hériter de B et C, je ne peux pas passer par super(). Je suis obligé d'écrire explicitement
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class D(B, C):
    	def __init__(self):
    		B.__init__(self)
    		C.__init__(self)
    		print("D.init")
    ???
    Je vois qu'on est tombé sur la même conclusion (voir mon post précédent)

  6. #6
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 721
    Points : 31 044
    Points
    31 044
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    Jusqu'à présent, je n'ai pas rencontré de problème avec cette méthode.
    Tu auras un souci si B et C héritent de A (cf mon tout premier exemple).

    Et si (en plus) leur héritage à eux est fait par super(), là ça partira complètement en c...
    Exemple
    Code python : 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
    class A:
    	def __init__(self):
    		print("A.init")
     
    class B(A):
    	def __init__(self):
    		print("1B")
    		super().__init__()
    		print("2B")
     
    class C(A):
    	def __init__(self):
    		print("1C")
    		super().__init__()
    		print("2C")
     
    class D(B, C):
    	def __init__(self):
    		print("1D")
    		B.__init__(self)
    		print("2D")
    		C.__init__(self)
    		print("3D")
     
    xxx=D()
    Résultat
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1D
    1B
    1C
    A.init
    2C
    2B
    2D
    1C
    A.init
    2C
    3D
    Avec "C" qui est lui-aussi intialisé 2 fois !!!

    Citation Envoyé par tyrtamos Voir le message
    Mais peut-être y a-t-il une solution avec "super"?
    Oui, il y en a une : rajouter super() dans tous tes __init__ y compris sur les objets de base (comme le montre mon 3° essai de mon 1er post)...
    Code python : 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
    class B:
    	def __init__(self):
    		super().__init__()
    		self.xb = "B"
    		print("B.init")
     
    class C:
    	def __init__(self):
    		super().__init__()
    		self.xc = "C"
    		print("C.init")
     
    class D(B, C):
    	def __init__(self):
    		super().__init__()
    		self.xd = "D"
    		print("D.init")
     
    xxx = D()
     
    print(xxx.xc, xxx.xb, xxx.xd)

    Résultat
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    B.init
    C.init
    D.init
    C B D
    Mais comme le dit wiztricks ça ne marche que sur __init__ (cf mon exemple avec action() qui plante quand on lui met un super()) et comme tu l'as vu quand j'ai tenté un héritage multiple sur le topic concernant les QThread, les objets "officiels" ne le font pas et donc tu ne peux pas en profiter.

  7. #7
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 893
    Points : 7 249
    Points
    7 249
    Par défaut
    Bonjour,

    Conceptuellement, il est rare d'avoir besoin d'héritage multiple, pour beaucoup de langage POO, ce n'est même pas possible de l'appliquer, c'est dire qu'il y a sans doute moyen de faire sans.

    Si j'essaye de suivre ta logique, D est un objet de type A et B, ce qui sous entend à mon sens que A est un objet de type B.
    Je l'écrirais donc de cette manière,

    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 B:    
        def __init__(self):
            print("B.init")
     
     
    class C(B):
        def __init__(self):
            super().__init__()
            print("C.init")
     
     
    class D(C):
        def __init__(self):
            super().__init__()
            print("D.init")
     
     
    xxx = D()

    Peut-être que D n'a pas besoin d'avoir toutes les fonctionnalités de B et C, dans ce cas, on pourrait initialiser les instances de B et C dans la fonction __init__ de D.

    Maintenant on sait que le multiple héritage est supporté par Python, le MRO est correct quand on fait un print(D.__mro__) et on voit qu'on a accès à l'ensemble des méthodes des classes B et C.
    En utilisant super, on pourrait se demander quelle super classe l'interpréteur choisira d'appeler (certainement pas les deux à la fois).
    On pourrait s'imaginer la même chose avec deux méthodes de même nom dans B et C. On est bien d'accord que pour faire appel à chacune d'elles, il nous faut connaître l'instance pour préciser quelle méthode on appelle. C'est pareil pour __init__ !
    Tu as accès aux méthodes B et C, il suffit donc d'appeler la/les méthode(s) __init__ que tu souhaites.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class B:
        def __init__(self):
            print("B.init")
    class C:
        def __init__(self):
            print("C.init")
     
     
    class D(B, C):
        def __init__(self):
            B.__init__(self)
            C.__init__(self)
            print("D.init")
    Pour moi, cette question a besoin d'avoir un objectif concret pour comprendre le besoin de l'héritage multiple.
    Je vois beaucoup ce type de syntaxe sur les développement Django... tu peux peut-être faire de la lecture de code sur ce framework, il est plutôt bien écrit et clair.

  8. #8
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 721
    Points : 31 044
    Points
    31 044
    Billets dans le blog
    1
    Par défaut
    Salut fred1599, merci de t'intéresser à ce topic

    Citation Envoyé par fred1599 Voir le message
    Conceptuellement, il est rare d'avoir besoin d'héritage multiple, pour beaucoup de langage POO, ce n'est même pas possible de l'appliquer, c'est dire qu'il y a sans doute moyen de faire sans.
    En réalité je n'en ai jamais (mais vraiment jamais) eu besoin.... jusqu'à ce topic.
    Pour résumer, tyrtamos a parlé d'une possibilité via un héritage multiple à partir d'un thread+QObject (possibilité non essentielle, je le précise, car un héritage simple à partir d'un QThread le fait très bien et là je te rejoins quand tu dis que c'est rarequasi-inutile) mais j'ai voulu le tenter (juste par curiosité).
    Et étant persuadé que super() savait gérer (cf mon exemple avec B et C qui héritent de A et D qui hérite de B et C et où ça fonctionne parfaitement) j'ai tenté cet héritage multiple via super(). Et ça n'a pas marché. D'où ce topic qui pose clairement l'interrogation.

    Citation Envoyé par fred1599 Voir le message
    Si j'essaye de suivre ta logique, D est un objet de type A et B, ce qui sous entend à mon sens que A est un objet de type B.
    Hum... Un hydravion est à la fois avion et à la fois bateau... mais un avion n'est pas un bateau... Ou un carré est à la fois losange et à la fois rectancle... mais un losange n'est pas un rectangle...

    Citation Envoyé par fred1599 Voir le message
    (certainement pas les deux à la fois).
    Jusqu'à la semaine dernière, j'avais la naïveté de le croire...
    Et je l'ai même sous-entendu dans mon tuto. Et je ne sais pas du tout comment rattrapper le coup pour détailler ce nouveau développement

    Citation Envoyé par fred1599 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class B:
        def __init__(self):
            print("B.init")
    class C:
        def __init__(self):
            print("C.init")
     
    class D(B, C):
        def __init__(self):
            B.__init__(self)
            C.__init__(self)
            print("D.init")
    Oui, on retombe toujours sur cette solution. Mais comme je l'ai souligné, si B et C héritent de A, le init de A est appelé deux fois (remarque restée sans réponse jusqu'à présent ).

    Citation Envoyé par fred1599 Voir le message
    Pour moi, cette question a besoin d'avoir un objectif concret pour comprendre le besoin de l'héritage multiple.
    https://www.developpez.net/forums/d2.../#post11891721

    Citation Envoyé par fred1599 Voir le message
    Je vois beaucoup ce type de syntaxe sur les développement Django... tu peux peut-être faire de la lecture de code sur ce framework, il est plutôt bien écrit et clair.
    D'autant plus qu'il m'intéresse et que j'aimerais savoir l'utiliser. Mais il y a tant de trucs qui m'intéressent...

    Merci de ton intervention. Même si j'ai critiqué (sans méchanceté) tes remarques, je l'apprécie

  9. #9
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 893
    Points : 7 249
    Points
    7 249
    Par défaut
    Je suis pas sûr que cette partie est été suffisamment claire

    En utilisant super, on pourrait se demander quelle super classe l'interpréteur choisira d'appeler (certainement pas les deux à la fois).
    On pourrait s'imaginer la même chose avec deux méthodes de même nom dans B et C. On est bien d'accord que pour faire appel à chacune d'elles, il nous faut connaître l'instance pour préciser quelle méthode on appelle. C'est pareil pour __init__ !
    En gros suivre l'ordre de recherche MRO, consiste à utiliser une boucle sur les classes dérivées en commençant par la classe la plus à gauche.
    À partir du moment où une classe __init__ a été trouvée, on exécute cette méthode de classe et on interrompt la boucle. Comme je l'ai dis précédemment, quand on appelle super on ne sait pas de quelle superclasse tu parles, le choix est automatique en python, on prend la classe dérivée la plus à gauche.

    Alors si j'essaye de résoudre ta problématique avec l'héritage de A dans B et C, je proposerai ceci

    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
    class A:    
        def __init__(self):
            print("A.init")
     
     
    class B(A):
        def __init__(self):
            print("B.init")
            super().__init__()
     
     
    class C(A):
        def __init__(self):
            print("C.init")
            super().__init__()
     
     
    class D(B, C):
        def __init__(self):
            print("D.init")
            super().__init__()
     
     
    xxx = D()
    pour suivre la MRO...

    Dans ton second 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
    class B:    
        def __init__(self):
            print("B.init")
     
     
    class C:
        def __init__(self):
            print("C.init")
     
     
    class D(B, C):
        def __init__(self):
            print("D.init")
            super().__init__()
            C.__init__(self)
    merci de t'intéresser à ce topic
    Ce genre de sujet ne m'intéresse pas, il me passionne

  10. #10
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 319
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 319
    Points : 36 829
    Points
    36 829
    Par défaut
    Citation Envoyé par fred1599 Voir le message
    Alors si j'essaye de résoudre ta problématique avec l'héritage de A dans B et C, je proposerai ceci
    Cet exemple est intéressant car il fait apparaitre le "diamant" (dans la hiérarchie) de façon plus explicite que dans l'exemple suivant où objet remplace A en silence.

    - W

  11. #11
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 721
    Points : 31 044
    Points
    31 044
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par fred1599 Voir le message
    Comme je l'ai dis précédemment, quand on appelle super on ne sait pas de quelle superclasse tu parles, le choix est automatique en python, on prend la classe dérivée la plus à gauche.
    Oui, il faut bien choisir un côté. Mais quelque chose me dit que ça peut se changer. Et donc celui qui s'y appuierait de façon systématique pour ses codes...

    Citation Envoyé par fred1599 Voir le message
    Alors si j'essaye de résoudre ta problématique avec l'héritage de A dans B et C, je proposerai ceci

    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
    class A:    
        def __init__(self):
            print("A.init")
     
    class B(A):
        def __init__(self):
            print("B.init")
            super().__init__()
     
     
    class C(A):
        def __init__(self):
            print("C.init")
            super().__init__()
     
     
    class D(B, C):
        def __init__(self):
            print("D.init")
            super().__init__()
     
     
    xxx = D()
    pour suivre la MRO...
    Weee !!!
    As-tu remarqué que c'est là mon tout premier exemple... (et aussi l'exemple de mon tuto)

    Citation Envoyé par fred1599 Voir le message
    Dans ton second exemple...
    En fait mes exemples ne sont pas à corriger, ils sont là pour montrer/illustrer les soucis qui sont (résumé)
    • si on appelle super() on n'hérite pas de tout (sauf si les ancètres passent eux-aussi par super()). Cf mon autre post où je tentais d'hériter d'un thread+QObject et où je n'hérite que du premier (le plus à gauche comme tu le dis)
    • si on appelle le __init__ manuellement, on hérite bien de tout mais on risque d'appeler deux fois le __init__ d'un grand-père éventuel commun aux deux
    • si on appelle le __init__ manuellement mais que les parents, eux, passent par super(), le truc part complètement en c...

    Me semble que c'est à peu près tout. Je cherche donc un exemple unique/global qui puisse solutionner tous ces soucis. Et plus on avance (en tournant en rond, il faut bien le dire), plus j'ai l'impression que cela n'est pas possible

  12. #12
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 893
    Points : 7 249
    Points
    7 249
    Par défaut
    Plus que mes exemples, c'est ce que j'annonce qui semble important, le reste n'est qu'une déduction qui "tombe bien" avec les exemples que tu donnes déjà...
    Mon exemple 2 corrige car il respecte la logique dîtes dans mes précédents messages.

    Cette problématique n'est liée qu'à l'appel de la méthode __init__ car ce nom de méthode est identique dans tes deux classes dérivées B et C.
    Pourquoi j'en déduis cela ? Simplement parce-que c'est une règle de la MRO, qui dit qu'une fois le nom de méthode trouvé, j'arrête la recherche de noms.

    Sans doute qu'un code avec des noms de méthodes différentes devrait fonctionner

    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
    class B:    
        def init_b(self):
            print("B.init")
     
     
    class C:
        def init_c(self):
            print("C.init")
     
     
    class D(B, C):
     
        def __init__(self):
            print("D.init")
            super().init_b()
            super().init_c()
     
     
    xxx = D()
    Mais quelque chose me dit que ça peut se changer
    En inversant B et C par C et B ?
    Sinon c'est une règle de conception du langage lui même, ça me semble compliqué.

    Me semble que c'est à peu près tout. Je cherche donc un exemple unique/global qui puisse solutionner tous ces soucis. Et plus on avance (en tournant en rond, il faut bien le dire), plus j'ai l'impression que cela n'est pas possible
    Non pas possible, tu dois suivre le MRO !
    Il y a sans doute des hacks, mais je suis pas certains que ça soit réellement la bonne démarche.

    Cet exemple est intéressant car il fait apparaitre le "diamant" (dans la hiérarchie) de façon plus explicite que dans l'exemple suivant où objet remplace A en silence.
    Effectivement !


    EDIT:

    Après réflexion, je pense avoir trouvé une solution qui permet de gérer les deux cas de figures,

    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
    class A:
        def __init__(self):
            print("A.init")
     
     
    class B(A):
        def __init__(self):
            print("B.init")
            super(A, self).__init__()
     
     
    class C(A):
        def __init__(self):
            print("C.init")
            super().__init__()
     
     
    class D(B, C):
        def __init__(self):
            print("D.init")
            super().__init__()
            super(B, self).__init__()
     
     
    xxx = D()
    et

    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 B:    
        def __init__(self):
            print("B.init")
     
     
    class C:
        def __init__(self):
            print("C.init")
     
     
    class D(B, C):
        def __init__(self):
            print("D.init")
            super().__init__()
            super(B, self).__init__()
     
     
    xxx = D()

  13. #13
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 721
    Points : 31 044
    Points
    31 044
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par fred1599 Voir le message
    Sans doute qu'un code avec des noms de méthodes différentes devrait fonctionner
    Exact
    Code python : 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
    class A:
    	def aaa(self): print("aaa")
     
    class B(A):
    	def bbb(self): print("bbb")
     
    class C(A):
    	def ccc(self): print("ccc")
     
    class D(B, C):
    	def aaa(self):
    		print("daaa")
    		super().aaa()
     
    	def bbb(self):
    		print("dbbb")
    		super().bbb()
     
    	def ccc(self):
    		print("dccc")
    		super().ccc()
     
    xxx = D()
    xxx.aaa()
    xxx.bbb()
    xxx.ccc()

    Citation Envoyé par fred1599 Voir le message
    En inversant B et C par C et B ?
    Non, je voulais parler de l'attribut statique de classe nommé "__mro__". J'imagine que si on change son contenu, ça doit influer sur la classe qui en hérite (mais je ne vais pas me lancer là dedans, déjà que j'ai du mal en restant dans les clous...)

    Citation Envoyé par fred1599 Voir le message
    Après réflexion, je pense avoir trouvé une solution qui permet de gérer les deux cas de figures,
    Effectivement, ça marche mais comment ça marche??? Je veux dire que la syntaxe de super() c'est super(nom_objet_courant, self). Or en appelant super(B, self) tu appelles super(nom_objet_ancètre, self) ???

  14. #14
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 893
    Points : 7 249
    Points
    7 249
    Par défaut
    J'imagine que si on change son contenu, ça doit influer sur la classe qui en hérite
    Je ne crois pas que se soit possible, ça fait partie de l'implémentation même du langage. Ce qui veut dire que cette méthode est déjà figé dans le marbre avant même l'appel de __new__ (je ne sais plus où j'ai lu ça).

    Effectivement, ça marche mais comment ça marche??? Je veux dire que la syntaxe de super() c'est super(nom_objet_courant, self). Or en appelant super(B, self) tu appelles super(nom_objet_ancètre, self) ???
    C'est une adaptation à tes besoins, en somme, j'aurai pu même retirer la ligne super(A, self).__init__() ligne 9.
    Le but étant de ne pas appeler la méthode __init__ deux fois pour la classe A.
    Mais si besoin tu peux faire cet appel, c'est seulement que j'ai eu l'impression que ce n'était pas un besoin.

    Par contre j'ai besoin d'appeler l'initialisation de A dans la classe D, pour cela, ma solution est d'appeler l'initialisation de C qui appelle l'initialisation de A.

    donc dans D, ma MRO est D -> B -> C -> A -> object
    B en 2ème position car j'appelle ma superclasse dans D. B étant à gauche des classes dérivées.

    Le tout est de bien comprendre qu'on essaie de suivre un cheminement, une règle qui est la MRO, et qu'elle doit être respectée. Ça n'a pas de sens conceptuel de faire autrement, ou à moins de me le prouver, d'un besoin très spécifique je ne vois pas l'intérêt.

    Ce qu'il faut bien comprendre, c'est que __init__ méthode spéciale sur du double héritage ne sera appelé que sur une classe et qu'il faut faire avec cette règle d'implémentation.

  15. #15
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 478
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 478
    Points : 9 278
    Points
    9 278
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Voilà un post très intéressant!

    C'est bien d'avoir pris un cas général, mais ça dépasse très nettement les besoins que j'avais exprimés.

    D'abord sur l'intérêt de faire de l'héritage multiple:
    Dans le cadre d'un programme graphique PyQt5, je voulais créer une classe thread, mais en exigeant qu'elle puisse envoyer des infos au graphique (.emit(...)). La solution QThread est évidente, et je n'utilise que celle-là. Mais pour ceux qui préfèrent le module python threading, j'ai évoqué la possibilité que la classe thread hérite non seulement de threading.Thread, mais aussi de QtCore.QObject, parce que c'est ce dernier qui permet à cette classe d'envoyer des infos au graphique (comme on le fait, par exemple, pour une barre de progression).

    Voilà donc traduit en code test la config envisagée:

    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
    import threading
    from PyQt5 import QtCore
     
    class D(threading.Thread, QtCore.QObject):
     
        def __init__(self, *args, **kwargs):
     
            threading.Thread.__init__(self, *args, **kwargs)
            QtCore.QObject.__init__(self)
     
            print("D.init")
     
    d = D()
     
    # affichage:
    D.init
    Ici, je n'utilise pas super(), mais l'initialisation des 2 ancêtres, ce qui me permet de transmettre les arguments d'appel aux bons ancêtres. Je n'utilise super() que pour les héritages simples.


    Si on calcule la MRO ("Method Resolution Order") de D, ainsi que celle des 2 ancêtres:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    print("MRO de D:", D.__mro__) # nom de class et non d'instance
    print("MRO du 1er ancêtre:", threading.Thread.__mro__)
    print("MRO du 2ème ancêtre:", QtCore.QObject.__mro__)
     
    # affichage:
    MRO de D: (<class '__main__.D'>, <class 'threading.Thread'>, <class 'PyQt5.QtCore.QObject'>, <class 'sip.wrapper'>, <class 'sip.simplewrapper'>, <class 'object'>)
    MRO du 1er ancêtre: (<class 'threading.Thread'>, <class 'object'>)
    MRO du 2ème ancêtre: (<class 'PyQt5.QtCore.QObject'>, <class 'sip.wrapper'>, <class 'sip.simplewrapper'>, <class 'object'>)
    On voit que les 2 ancêtres héritent de "object", ce qui est normal pour Python. mais aucune autre classe n'est commune.


    On peut vérifier qu'on accède bien aux attributs spécifiques aux 2 ancêtres:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    print("is_alive:", d.is_alive()) # appel d'une méthode de threading.Thread 
     
    d.setObjectName("toto") # appel d'une méthode de QtCore.QObject
    print("objectName:", d.objectName())# idem
     
    # affichage:
    is_alive: False
    objectName: toto

    On peut vérifier s'il existe des attributs communs aux 2 ancêtres, qui obligeraient l'algorithme MRO à choisir celui qui restera:

    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
    attrib1 = sorted(list(dir(threading.Thread))) # liste des attributs de threading.Thread
    attrib2 = sorted(list(dir(QtCore.QObject))) # liste des attributs de QtCore.QObject
    print("attrib1:", attrib1)
    print(len(attrib1))
    print("attrib2:", attrib2)
    print(len(attrib2))
     
    communs = sorted(list(set(attrib1) & set(attrib2)))
    print("Attributs communs:", communs) # attributs communs aux 2 ancêtres
    print(len(communs))
     
    # affichage:
    attrib1: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bootstrap', '_bootstrap_inner', '_delete', '_initialized', '_reset_internal_locks', '_set_ident', '_set_native_id', '_set_tstate_lock', '_stop', '_wait_for_tstate_lock', 'daemon', 'getName', 'ident', 'isDaemon', 'is_alive', 'join', 'name', 'native_id', 'run', 'setDaemon', 'setName', 'start']
    48
    attrib2: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'blockSignals', 'childEvent', 'children', 'connectNotify', 'customEvent', 'deleteLater', 'destroyed', 'disconnect', 'disconnectNotify', 'dumpObjectInfo', 'dumpObjectTree', 'dynamicPropertyNames', 'event', 'eventFilter', 'findChild', 'findChildren', 'inherits', 'installEventFilter', 'isSignalConnected', 'isWidgetType', 'isWindowType', 'killTimer', 'metaObject', 'moveToThread', 'objectName', 'objectNameChanged', 'parent', 'property', 'pyqtConfigure', 'receivers', 'removeEventFilter', 'sender', 'senderSignalIndex', 'setObjectName', 'setParent', 'setProperty', 'signalsBlocked', 'startTimer', 'staticMetaObject', 'thread', 'timerEvent', 'tr']
    69
    communs pour 1 et 2: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
    26
    On voit que les 2 ancêtres ne se partagent que les attributs de structure ("__xxx__"), caractéristiques des classes Python.


    Dernier point. On voit que __doc__ est commun aux 2 ancêtres. On peut d'ailleurs les afficher:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    print("doc de threading.Thread:", threading.Thread.__doc__)
    print("doc de QtCore.QObject:", QtCore.QObject.__doc__)
     
    # affichage:
    doc de threading.Thread: A class that represents a thread of control.
     
        This class can be safely subclassed in a limited fashion. There are two ways
        to specify the activity: by passing a callable object to the constructor, or
        by overriding the run() method in a subclass.
     
     
    doc de QtCore.QObject: QObject(parent: QObject = None)
    Mais si je demande la doc de D, je trouve:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    print("doc de D:", D.__doc__)
     
    # affichage:
    doc de D: None
    Donc, ici, l'algorithme MRO aurait dû choisir pour la doc de D, celle du 1er ancêtre threading.Thread, mais il ne l'a pas fait! Concernant la doc, ce n'est pas une mauvaise décision: la doc de D doit lui être spécifique. Est-ce que ça fait partie de l'algorithme MRO?

    Essayons avec __dict__:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    print(threading.Thread.__dict__)
    print(QtCore.QObject.__dict__)
    print(D.__dict__)
     
    # affichage:
    {'__module__': 'threading', '__doc__': 'A class that represents a thread of control.\n\n    This class can be safely subclassed in a limited fashion. There are two ways\n    to specify the activity: by passing a callable object to the constructor, or\n    by overriding the run() method in a subclass.\n\n    ', '_initialized': False, '__init__': <function Thread.__init__ at 0x000001FF78DB9940>, '_reset_internal_locks': <function Thread._reset_internal_locks at 0x000001FF78DB99D0>, '__repr__': <function Thread.__repr__ at 0x000001FF78DB9A60>, 'start': <function Thread.start at 0x000001FF78DB9AF0>, 'run': <function Thread.run at 0x000001FF78DB9B80>, '_bootstrap': <function Thread._bootstrap at 0x000001FF78DB9C10>, '_set_ident': <function Thread._set_ident at 0x000001FF78DB9CA0>, '_set_native_id': <function Thread._set_native_id at 0x000001FF78DB9D30>, '_set_tstate_lock': <function Thread._set_tstate_lock at 0x000001FF78DB9DC0>, '_bootstrap_inner': <function Thread._bootstrap_inner at 0x000001FF78DB9E50>, '_stop': <function Thread._stop at 0x000001FF78DB9EE0>, '_delete': <function Thread._delete at 0x000001FF78DB9F70>, 'join': <function Thread.join at 0x000001FF78DBC040>, '_wait_for_tstate_lock': <function Thread._wait_for_tstate_lock at 0x000001FF78DBC0D0>, 'name': <property object at 0x000001FF78DB2EA0>, 'ident': <property object at 0x000001FF78DB2DB0>, 'native_id': <property object at 0x000001FF78DB2E00>, 'is_alive': <function Thread.is_alive at 0x000001FF78DBC3A0>, 'daemon': <property object at 0x000001FF78DB2F90>, 'isDaemon': <function Thread.isDaemon at 0x000001FF78DBC550>, 'setDaemon': <function Thread.setDaemon at 0x000001FF78DBC5E0>, 'getName': <function Thread.getName at 0x000001FF78DBC670>, 'setName': <function Thread.setName at 0x000001FF78DBC700>, '__dict__': <attribute '__dict__' of 'Thread' objects>, '__weakref__': <attribute '__weakref__' of 'Thread' objects>}
    {'__module__': 'PyQt5.QtCore', '__getattr__': <built-in method __getattr__>, '__weakref__': <attribute '__weakref__' of 'QObject' objects>, '__doc__': 'QObject(parent: QObject = None)', 'blockSignals': <built-in method blockSignals>, 'childEvent': <built-in method childEvent>, 'children': <built-in method children>, 'connectNotify': <built-in method connectNotify>, 'customEvent': <built-in method customEvent>, 'deleteLater': <built-in method deleteLater>, 'disconnect': <built-in method disconnect>, 'disconnectNotify': <built-in method disconnectNotify>, 'dumpObjectInfo': <built-in method dumpObjectInfo>, 'dumpObjectTree': <built-in method dumpObjectTree>, 'dynamicPropertyNames': <built-in method dynamicPropertyNames>, 'event': <built-in method event>, 'eventFilter': <built-in method eventFilter>, 'findChild': <built-in method findChild>, 'findChildren': <built-in method findChildren>, 'inherits': <built-in method inherits>, 'installEventFilter': <built-in method installEventFilter>, 'isSignalConnected': <built-in method isSignalConnected>, 'isWidgetType': <built-in method isWidgetType>, 'isWindowType': <built-in method isWindowType>, 'killTimer': <built-in method killTimer>, 'metaObject': <built-in method metaObject>, 'moveToThread': <built-in method moveToThread>, 'objectName': <built-in method objectName>, 'parent': <built-in method parent>, 'property': <built-in method property>, 'pyqtConfigure': <built-in method pyqtConfigure>, 'receivers': <built-in method receivers>, 'removeEventFilter': <built-in method removeEventFilter>, 'sender': <built-in method sender>, 'senderSignalIndex': <built-in method senderSignalIndex>, 'setObjectName': <built-in method setObjectName>, 'setParent': <built-in method setParent>, 'setProperty': <built-in method setProperty>, 'signalsBlocked': <built-in method signalsBlocked>, 'startTimer': <built-in method startTimer>, 'thread': <built-in method thread>, 'timerEvent': <built-in method timerEvent>, 'tr': <built-in method tr>, 'staticMetaObject': <sip.variabledescriptor object at 0x000001FF7A7DECC0>, 'objectNameChanged': <unbound PYQT_SIGNAL objectNameChanged(QString)>, 'destroyed': <unbound PYQT_SIGNAL destroyed(QObject*)>}
    {'__module__': '__main__', '__init__': <function D.__init__ at 0x000001FF78F6BCA0>, '__doc__': None}
    On voit que pour __dict__, commun aux 2 ancêtres, celui de D lui est spécifique, ce qui, encore une fois est logique. Peut-être y a-t-il d'autres attributs de structure qui sont traités comme ça? Voire tous?

  16. #16
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 893
    Points : 7 249
    Points
    7 249
    Par défaut
    Bonjour @tyrtamos,

    Ton code exemple si tu as suivi mes explications serait équivalent à

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import threadingfrom PyQt5 import QtCore
     
     
    class D(threading.Thread, QtCore.QObject):
     
        def __init__(self, *args, **kwargs):
            super().__init__()
            super(threading.Thread, self).__init__()
     
            print("D.init")
     
     
    d = D()
    j'ai évoqué la possibilité que la classe thread hérite non seulement de threading.Thread, mais aussi de QtCore.QObject, parce que c'est ce dernier qui permet à cette classe d'envoyer des infos au graphique (comme on le fait, par exemple, pour une barre de progression)
    Rien dans cette phrase permet de justifier (même avec threading) le double héritage à mon sens. Pourquoi ne pas utiliser une instance de QObject dans un paramètre de __init__ par exemple ? Est-ce que toutes les méthodes de QObject sont utiles à la classe D ? Je ne sais pas ce qu'est D concrètement et ce qu'il est censé faire...

  17. #17
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 478
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 478
    Points : 9 278
    Points
    9 278
    Billets dans le blog
    6
    Par défaut
    Bonjour fred1599,


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        def __init__(self, *args, **kwargs):
            super().__init__()
            super(threading.Thread, self).__init__()
    Le problème de super(), c'est qu'on n'est pas sûr de ce qu'il fait réellement ! Tu utilises un super() général, suivi d'un super spécifique à l'un des ancêtres. Qu’est ce que ça fait? Et comment passer les arguments? Ceci serait plus logique:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        def __init__(self, *args, **kwargs):
            super(threading.Thread, self).__init__(*args, **kwargs)
            super(QtCore.QObject, self).__init__()


    Citation Envoyé par fred1599 Voir le message
    Rien dans cette phrase permet de justifier (même avec threading) le double héritage à mon sens. Pourquoi ne pas utiliser une instance de QObject dans un paramètre de __init__ par exemple ? Est-ce que toutes les méthodes de QObject sont utiles à la classe D ? Je ne sais pas ce qu'est D concrètement et ce qu'il est censé faire...
    Il me semblait que je m'étais expliqué là-dessus. Dans un programme graphique, les tâches longues non graphiques doivent être déléguées à un thread, sinon, le graphique se fige, et on ne peut plus intervenir, même pour arrêter la tâche longue. Mais si ce thread doit renseigner le graphique comme on le fait, par exemple, pour mettre à jour une barre de progression, il ne doit pas toucher directement au graphique car celui-ci n'est pas "thread-safe" (sinon, plantage aléatoire). La seule méthode pour que le thread envoie des infos au graphique, qui seront traitées par la gestion des évènements, c'est de créer un nouveau signal et d'envoyer les infos par ce signal avec ".emit(...). En créant une classe avec QThread, ça marche tout seul puisque QThread hérite de QObject. Mais si on utilise threading.Thread, ça ne marche plus parce qu'il n'a jamais été prévu que le module threading fasse ça. La seule solution, c'est donc de créer une classe thread avec l'héritage multiple "threading.Thread + QtCore.QObject", et c'est ce dernier qui permet d'envoyer des infos au graphique.

    Quand à la classe D, tu l'appelles comme tu veux. MonThread par exemple.

    Il reste que j'utilise rarement cette solution, et je préfère utiliser directement QThread (héritage unique) mais il faut apprendre à s'en servir!. En tout cas, cette solution de classe à héritage multiple fonctionne très bien!

  18. #18
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 893
    Points : 7 249
    Points
    7 249
    Par défaut
    Donc si je comprends bien...

    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
    import threading
    from PyQt5 import QtCore
     
     
    class D(threading.Thread):
     
        def __init__(self, *args, **kwargs):
            super().__init__()
     
            self.obj = QtCore.QObject()
     
            print("D.init")
     
     
    d = D()
    le code ci-dessus ne fonctionnera pas pour l'affichage des graphiques ?

  19. #19
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 478
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 478
    Points : 9 278
    Points
    9 278
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par fred1599 Voir le message
    le code ci-dessus ne fonctionnera pas pour l'affichage des graphiques ?
    Merci fred1599 pour l'idée. Je vais essayer laisse moi un peu de temps.

  20. #20
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 721
    Points : 31 044
    Points
    31 044
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par fred1599 Voir le message
    le code ci-dessus ne fonctionnera pas pour l'affichage des graphiques ?
    On dévie un peu du sujet initial mais ce n'est pas grave, je crois que le résumé de tyrtamos "Le problème de super(), c'est qu'on n'est pas sûr de ce qu'il fait réellement !" est parfait pour clôre le débat. Surtout pour un truc que je n'ai jamais fait, que je n'envisageais pas de faire un jour mais que désormais je m'attacherai à éviter de faire préférant plutôt me couper les mains

    Le principal souci de Qt face aux threads, c'est que la mise à jour de l'IHM ne peut se faire que dans l'IHM dite "principale", celle qui appelle le exec_(). Un thread n'a pas le droit de dire "ok le QPushButton, va te cacher" ou bien "ok le QProgressBar, avance un peu" même s'il possède la référence du QPushButton ou du QProgressBar.
    S'il veut transmettre une info à Qt, il ne peut le faire que via signal.emit() ; signal que l'IHM peut récupérer et traiter dans un slot qui fera cacher le QPushButton ou progresser le QProgressBar.

    Donc la question se résume à "comment ce self.obj peut-il envoyer un signal" et "comment, du côté Qt, associer un signal venu de D.obj à un slot local" ??? On peut rajouter une question moins essentielle mais qui mérite d'apparaitre et qui serait "pourquoi un thread irait créer un QObject alors qu'un QThread est déjà un thread avec QObject intégré et permet de répondre directement aux deux questions précédentes"

    Si ça t'intéresse, je te recommande cet exemple approuvé par tyrtamos

Discussions similaires

  1. composants C++ Builder et héritage multiple
    Par vedrfolnir dans le forum C++Builder
    Réponses: 2
    Dernier message: 12/10/2005, 10h04
  2. [heritage][conception]héritage multiple en java!
    Par soulhouf dans le forum Langage
    Réponses: 9
    Dernier message: 25/08/2005, 20h03
  3. L'héritage multiple est-il possible en Delphi ?
    Par SchpatziBreizh dans le forum Langage
    Réponses: 8
    Dernier message: 30/06/2005, 11h30
  4. utilisez vous l'héritage multiple ?
    Par vodosiossbaas dans le forum C++
    Réponses: 8
    Dernier message: 13/06/2005, 20h25
  5. [XML Schemas]héritage multiple
    Par nicolas_jf dans le forum XML/XSL et SOAP
    Réponses: 2
    Dernier message: 10/06/2003, 12h55

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