Forum Programmation.python Ajouter des méthodes à une instance de classe, après sa création

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
2
20
jan.
2018

Salut
Je cherche à ajouter des méthodes à une instance de classe, après sa création.

J'ai par exemple

un fichier moto.py

def tourner():
    tourner_guidon();

un fichier voiture.py

def tourner():
    tourner_volant()

un fichier main.py

class Vehicule():
    def __init__(self, categorie):
        pass

v = Vehicule('moto')
if v.categorie == 'voiture':
    # attacher voiture.tourner()
elif v.categorie == 'moto':
    # attacher moto.tourner()

#Pour tourner
v.tourner() # Sans me soucier de la catégorie de véhicule

J'espère être clair….
Et non, ce n'est pas un exercice de cours….

Merci.

David.

  • # Prendre le problème à l'envers

    Posté par  . Évalué à 7.

    Salut,

    Même en ayant l'habitude des langages (pré-)compilés où ce genre de manip' est impossible, ajouter dynamiquement des méthodes à une classe est déjà douteux, mais les ajouter à une instance précise est encore plus tordu. Pourtant, l'exercice aurait pu être intéressant en soi…

    Mais dans le cas qui te concerne aujourd'hui, c'est un simple problème de conception objet : il faut définir une classe Véhicule, puis deux classes Voitures et Moto qui dérivent toutes les deux de la classe véhicule.

    class Vehicule:
        def tourner(self):
            print("Vehicule qui tourne")
    
    class Moto(Vehicule):
        def tourner(self):
            print("Moto qui tourne")
    
    class Voiture(Vehicule):
        def tourner(self):
            print("Voiture qui tourne")
    
    class Autogyre(Vehicule):
        def voler(self):
            print("Autogyre qui vole");
    
    h = Vehicule()
    m = Moto()
    v = Voiture()
    a = Autogyre()
    
    h.tourner()
    m.tourner()
    v.tourner()
    a.tourner()
    

    Ce qui donne :

    Vehicule qui tourne
    Moto qui tourne
    Voiture qui tourne
    Vehicule qui tourne
    

    Dans cet exemple, les classes Voiture et Moto dérivent de Véhicule, mais redéfinissent la méthode « tourner », et c'est cette dernière version qui est donc appelée. En revanche, la classe Autogyre dérive également de Véhicule mais définit une autre méthode sans redéfinir « tourner ». C'est donc la version par défaut héritée de « Véhicule » qui est appelée, et c'est pourquoi on obtient « Véhicule qui tourne » et non « Autogyre qui tourne ».

    • [^] # Re: Prendre le problème à l'envers

      Posté par  . Évalué à 2.

      Bonjour,

      Indépendamment du problème de conception signalé plus haut c'est techniquement faisable:

      if v.categorie == 'voiture':
          v.tourner = voiture.tourner
          # attacher voiture.tourner()
      elif v.categorie == 'moto':
          v.tourner = moto.tourner
      • [^] # Re: Prendre le problème à l'envers

        Posté par  . Évalué à 1.

        Merci.
        Mais je voudrais que "tourner" soient des méthodes pour pouvoir manipuler l'instance.

        Je trouvais ça plus élégant que de passer mon objet à une fonction.

        • [^] # Re: Prendre le problème à l'envers

          Posté par  . Évalué à 2.

          Je trouvais ça plus élégant que de passer mon objet à une fonction.

          c'est pourtant bien plus logique, non ?
          Faire tourner (voiture) = agir sur le volant
          Faire tourner (moto) = agir sur le guidon

    • [^] # Re: Prendre le problème à l'envers

      Posté par  . Évalué à 2. Dernière modification le 21 janvier 2018 à 16:57.

      Même en ayant l'habitude des langages (pré-)compilés où ce genre de manip' est impossible, ajouter dynamiquement des méthodes à une classe est déjà douteux, mais les ajouter à une instance précise est encore plus tordu.

      C'est tout simplement parce que tu vois tous les problèmes comme des clous à planter.

      Je suis en train d'essayer de traiter une problématique pur laquelle il serait très pratique de pouvoir modifier à la volée une classe (j'en suis pas encore à l'instance de classe). J'essaie de le faire en python, mais je me rends compte que c'est presque impossible. Le seul langage (que e connaisse car il y en a probablement d'autres) qui me permettrait de le faire serait ruby, ou alors de me passer de l'approche objet.

      Alors certes, c'est risqué, je ne le ferais absolument pas en n'importe quelle circonstances, mais dans le cas que je traite (définition des classes à partir de spécifications fournies par un fichier yaml), c'est bien pratique. Il est probablement possible de faire autrement de façon plus classique, mais ça serait beaucoup plus compliqué à implémenter.

      • [^] # Re: Prendre le problème à l'envers

        Posté par  . Évalué à 2.

        Bon : je reformule : il faut remplacer

        je me rends compte que c'est presque impossible.

        par

        je me rends compte que c'est très compliqué avec ce lanagage

      • [^] # Re: Prendre le problème à l'envers

        Posté par  . Évalué à 4.

        Salut,

        C'est tout simplement parce que tu vois tous les problèmes comme des clous à planter.

        Je ne suis pas sûr d'avoir complètement compris ce que tu entendais par là, mais si tu voulais dire « une manière de parvenir à ses fins, indépendamment de la curiosité mathématique », alors non. C'est même plutôt le contraire, si tu relis bien mon commentaire. C'est intéressant dans les langages tels que le Javascript qui sont non seulement interprétés, mais où les objets sont définis et remplis à l'exécution.

        Par contre, il est important de ne pas faire tout et n'importe quoi juste pour résoudre le problème, même de façon théorique (ce qui nous ramènerait aux clous à planter). En développement logiciel, je suis assez adepte du « changement de paradigme », c'est-à-dire se laisser la possibilité d'utiliser un jeu de concepts totalement exotiques s'il le faut mais À CONDITION de poser le cadre dès le départ et de ne pas en sortir a posteriori au cours d'un projet, surtout si c'est pour se sortir d'une impasse.

        En particulier, il y a beaucoup de gens qui font l'amalgame « classe = objet », alors que le mot « classe » doit en fait s'entendre dans le même sens que « classification du règne animal », par exemple. En ce sens, si tu ajoutes ou retires quelque chose à une classe, alors par définition, ce n'est plus la même classe ! Et ça pose problème avec les objets déjà instanciés.

        Plus précisément, si l'on modifie une classe, c'est en fait l'ontologie elle-même que l'on modifie, même si cette ontologie est maintenue grâce à des données définies à l'exécution (et qui sont en fait des méta-données). Ça reste très séduisant en soi, mais c'est pour moi du même niveau que le code auto-généré. Ça n'est à utiliser qu'à partir du moment où tous les concepts initiaux sont parfaitement clairs et que, de là, on sait parfaitement ce que l'on fait.

        Et là, il me semblait justement que le message du primo-postant n'avait pas vocation à débattre de ce sujet, mais était bien confronté à un cas de figure fondamental en programmation orientée objet, d'où la réponse qui me semblait être la plus appropriée.

        • [^] # Re: Prendre le problème à l'envers

          Posté par  . Évalué à 3.

          je suis assez adepte du « changement de paradigme », c'est-à-dire se laisser la possibilité d'utiliser un jeu de concepts totalement exotiques s'il le faut mais À CONDITION de poser le cadre dès le départ et de ne pas en sortir a posteriori au cours d'un projet, surtout si c'est pour se sortir d'une impasse.

          Je pense que sur le fond nous sommes d'accord, mais que je n'ai pas forcément saisi le sens de ta remarque à la première lecture. Je voulais juste préciser que bien qu'en général la modification dynamique d'une classe peu sembler être une mauvaise idée, il ne faut pas être dogmatique et savoir s'adapter aux problèmes à traiter.

          Plus précisément, si l'on modifie une classe, c'est en fait l'ontologie elle-même que l'on modifie, même si cette ontologie est maintenue grâce à des données définies à l'exécution (et qui sont en fait des méta-données).

          C'est grosso mode le cas de figure que j'essaie de traiter. Qui dit méta-données dit méta-programmation, et c'est précisément ce que je tente de faire (et python est assez limité de ce côté).

          Ça reste très séduisant en soi, mais c'est pour moi du même niveau que le code auto-généré. Ça n'est à utiliser qu'à partir du moment où tous les concepts initiaux sont parfaitement clairs et que, de là, on sait parfaitement ce que l'on fait.

          Tout à fait. Dans mon cas, je n'aurais pas la prétention de savoir parfaitement ce que je fais (pour tout dire c'est plutôt de l'expérimentation), mais ce que je sais, c'est qu'il est évident qu'une approche objet "classique" n'est pasl a solution la meilleure.

    • [^] # Re: Prendre le problème à l'envers

      Posté par  . Évalué à 3.

      Pour répondre à la problématique mot-à-mot de l'auteur, en langage compilé pour modifier le comportement d'un objet selon son instance, j'aurais soit utilisé:

      • un tableau associatif "nom=>callback" (si on parle d'ajouter à la volée et en fonction de l'instance, mais je trouve ça sale)
      • plus probable, un design pattern, probablement le décorateur: en gros, on ajoute à la classe «maîtresse» une instance (on un conteneur d'instances) d'une classe dont dérivent d'autres classes implémentant les comportements souhaités,
      • des fonctions qui manipulent la classe, plutôt qu'embarquer un comportement volatile directement.

      Tout ça pour dire que ce n'est pas un problème de langage compilé, et ce genre de techniques sont très utiles dès lors que l'on implémente des mécanismes de plug-ins, par exemple.

    • [^] # Re: Prendre le problème à l'envers

      Posté par  . Évalué à 3.

      Même en ayant l'habitude des langages (pré-)compilés où ce genre de manip' est impossible, ajouter dynamiquement des méthodes à une classe est déjà douteux, mais les ajouter à une instance précise est encore plus tordu. Pourtant, l'exercice aurait pu être intéressant en soi…

      Un exercice pas bien compliqué puisqu'il suffit d'essayer. C'est sûr que c'est douteux mais il y a plein de scénario ou ce genre de possibilité est utile. Le duck-typing en profite énormément, si une bibliothèque nécessite un objet capable de d'aboyer mais que je suis sûr d'avoir besoin et qu'il est pertinent de l'appliquer à mes canards, rien de plus simple que de faire aboyer mes canards.

      Ce n'est pas quelque chose qu'on souhaite croiser en production mais tout le monde n'a pas des problèmes d'ingénierie logicielle à résoudre.

      
      >>> class C():
      ...     pass
      ...
      >>> i1=C()
      >>> i2=C()
      >>> def f():
      ...     print("ok")
      ...
      >>> i1.f = f
      >>> i1.f()
      ok
      >>> i2.f()
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: C instance has no attribute 'f'
      
  • # Merci pour vos réponses

    Posté par  . Évalué à 1.

    :)

  • # Difficulté ?

    Posté par  (site web personnel) . Évalué à 3. Dernière modification le 22 janvier 2018 à 14:06.

    >>> class X: pass
    ... 
    >>> a = X()
    >>> b = X()
    >>> def fcta(): print("Je suis fcta")
    ... 
    >>> def fctb(): print("Je suis fctb")
    ... 
    >>> a.fct = fcta
    >>> b.fct = fctb
    >>> a.fct()
    Je suis fcta
    >>> b.fct()
    Je suis fctb

    Mais la bonne façon de faire (au niveau POO) serait d'avoir une sous-class Moto et une sous-classe Voiture, héritant toutes deux de la classe Vehicule, puis de définir une méthode générique tourner() qui est redéfinie dans chaque sous-classe.
    Un fonction factory pourrait être utilisée pour créer Moto ou Voiture suivant un paramètre catégorie. Tu peux en faire qq chose de dynamique dans lequel les modules moto.py et voiture.py vont enregistrer quelque part l'association ente la catégorie et la classe ad-hoc à créer.

    Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

    • [^] # Re: Difficulté ?

      Posté par  (site web personnel) . Évalué à 2. Dernière modification le 22 janvier 2018 à 14:21.

      Complément.

      Si tu veux passer l'objet en paramètre à tes fonctions isolées (pour accéder à ses attributs), tu peux utiliser la fonction partial() du module functools, définir un paramètre self dans tes fonctions isolées et faire que l'objet auquel est associé la fonction soit passé comme paramètre). Du genre (pas testé):

      def fcta(self): print("Je suis", self, "dans fcta")
      
      a.fct = functools.partial(fcta, a)

      Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.