Forum Programmation.python Je cherche à créer une expèce de proxy

Posté par . Licence CC by-sa
Tags : aucun
1
18
avr.
2014

Bonjour.

J'essaie de créer un proxy en python. Le but est de pouvoir utiliser mon objet comme l'objet qu'il encapsule.

J'ai commencé par ça (le code n'est peut-être pas propre, plein de commentaires oui d'affichage bidon, mais c'est pour voir ce qui se passe : il ne passera pas en prod mais me sert à comprendre ce qui se passe):

    #!/usr/bin/python

    class MyClass(object):

        def __init__(self,_class=None):
            print("Initialization of class MyClass without args\n")
            o=_class()
            print("Type object : " + str(o.__class__()))
            self.__dict__['_obj']=o

        def __getattr__(self,name):
            def _missing(*args, **kwargs):
                print "A missing method was called."
                print "The object was %r, the method was %r. " % (self, name)
                print "It was called with %r and %r as arguments" % (args, kwargs)
                return getattr(self.__dict__['_obj'],name)
            return _missing



    c=MyClass(list)
    x=c.append()
    print ("value : " + str(c[0]))

A l'exécution, j'ai ce message :

        ./test1.py 
        Initialization of class MyClass without args

        Type object : []
        A missing method was called.
        The object was <__main__.MyClass object at 0x7f9c4aa63ad0>, the method was 'append'. 
        It was called with () and {} as arguments
        Traceback (most recent call last):
          File "./test1.py", line 27, in <module>
            print ("value : " + str(c[0]))
        TypeError: 'MyClass' object does not support indexing

Ce que j'en ai compris, c'est que je n'ai pas implémenté la méthode __getitem__ dans ma classe.
Or, je croyais que la méthode non implémentée serait intercptée par __getattr__ , mais il semble que je me sois trompé.

Y aurait-il un autre niveau ou je pourrais intercepter toutes les méthodes définies, pour pouvoir les rediriger vers mon objet ?

En ruby, je peux le faire :

    #!/usr/bin/ruby
    #
    #

    class Test

        def initialize()
            end
        def initialize(obj)
            @obj=obj.new()
        end

        def method_missing(meth,*args,&block)
                puts("Method: #{meth}, args : #{args}")
                @obj.send(meth.to_sym,*args)
        end
    end

    x=Test.new(Array)
    x.push("abc")
    x.push("def")
    puts("x[0]: #{x[0]}")

Le résultat :

    $ ruby test1.rb
    Method: push, args : abc
    Method: push, args : def
    Method: [], args : 0
    x[0]: abc

Merci d'avance.

  • # encapsulation ou héritage ?

    Posté par . Évalué à 2.

    je vois que tu es passé par l'encapsulation pour faire ton proxy. Mais ca pourrait aussi se faire par le biais de l'héritage, sauf qu'il faut connaître à l'avance la classe de l'objet. Dans ton cas, dans la class Test, en ruby, tu passes la class, puis c'est une instance d'objet qui est faite.

    • [^] # Re: encapsulation ou héritage ?

      Posté par . Évalué à 2.

      Mais ca pourrait aussi se faire par le biais de l'héritage, sauf qu'il faut connaître à l'avance la classe de l'objet

      Je ne préfère pas passer par l'héritage : je souhaite ajouter d'autres propriétés à ma classe. Entre autres, je souhaiterais pouvoir ajouter un lock pour permettre à mes classes encapsulées d'être thread_safe, plus d'autres infos, mais en gardant le comportement "normal" de l'objet encapsulé : ici c'est une liste mais j'ai d'autres objets à utiliser de cette façon.

    • [^] # Re: encapsulation ou héritage ?

      Posté par . Évalué à 2.

      Dans ton cas, dans la class Test, en ruby, tu passes la class, puis c'est une instance d'objet qui est faite.

      Désolé, je ne comprends pas ce que tu veux dire. Peut-être parce qu'il est tard et que j'ai passé une bonne partie de la journée à chercher une solution à ce problème. Pourrais-tu détailler un peu STP ?

  • # quelques remarques

    Posté par . Évalué à 2. Dernière modification le 18/04/14 à 19:35.

    Attention, __getattr__ est censé renvoyé la valeur d'un attribut. Ton implémentation actuelle renvoie une méthode qui renvoie la valeur de l'attribut pour _obj. Est-ce que tu ne voudrais pas plutôt faire ? :

        def __getattr__(self,name):
            return getattr(self.__dict__['_obj'],name)

    Pour l'appel direct c[0], de ce que j'ai testé, l'interpréteur utilise la méthode __getitem__, mais en l'appelant directement, sans passer par __getattr__. Du coup, tu peux t'en sortir en ajoutant la méthode:

        def __getitem__(self, key):
            return self.__dict__['_obj'].__getitem__(key)

    Si tu veux pouvoir affecter un élément de la liste, il faut que tu ajoutes aussi __setitem__. Bref, pour faire propre, il faudrait ajouter tous les __<methode>__ de la classe qui t'intéresse (inclus __repr__ et __str__ pour faire joli).

    Mais du coup, tu te retrouves avec un proxy spécialisé pour les listes, ce qui n'est pas forcément ce que tu veux. L'étape d'après est de lister tous les attributs de la classes que tu veux proxifier, et rajouter les méthode __*__ dont tu as besoin à l'instanciation. Et du coup, il y a surement une recette quelque part sur le net.

    Edit: je vois que je répond un peu à côté de la question sur les __getitem__ et compagnie

    • [^] # Re: quelques remarques

      Posté par . Évalué à 2.

      Si tu veux pouvoir affecter un élément de la liste, il faut que tu ajoutes aussi setitem. Bref, pour faire propre, il faudrait ajouter tous les ____ de la classe qui t'intéresse (inclus repr et str pour faire joli).

      C'est justement ce que je n'ai pas envie de faire.

      Mais du coup, tu te retrouves avec un proxy spécialisé pour les listes, ce qui n'est pas forcément ce que tu veux.

      Exactement. Je voudrais pouvoir faire ça sur d'autres objets.

      L'étape d'après est de lister tous les attributs de la classes que tu veux proxifier, et rajouter les méthode * dont tu as besoin à l'instanciation. Et du coup, il y a surement une recette quelque part sur le net.

      Si elle existe, je ne l'ai pas vue, mais j'ai peut-être mal cherché.

      Edit: je vois que je répond un peu à côté de la question sur les getitem et compagnie

      Non, en fait tu es en plein dedans : tu as compris exactement ce que je voulais. En fait je voudrais quelque chose d'applicable à toute méthode de l'instance à proxifier. J'ai eu un problème avec getitem, mais j'ai vite compris que j'aurais des problèmes avec d'autres méthodes, et je recherche une méthode "générique", comme l'exemple que j'ai montré en Ruby.

      En tout cas merci : même si tu n'as pas proposé de solution à mon problème, tu l'as clairement identifié.

      • [^] # Re: quelques remarques

        Posté par . Évalué à 2. Dernière modification le 19/04/14 à 01:12.

        Si elle existe, je ne l'ai pas vue, mais j'ai peut-être mal cherché.

        En fait, j'ai pas trouvé non plus, mais c'est p-e parce que c'est pas comme ça qu'on ferait en Python. Tu peux peut-être t'en sortir avec un décorateur qui fabriquerait à la volée des classes dérivées des types qui t'intéressent avec les ajustements dont tu as besoin. Autrement dit, au lieu d'encapsuler ta classe dans un proxy générique, est-ce que ça serait pas plus simple d'avoir un générateur de classes proxy spécialisées ?

        • [^] # Re: quelques remarques

          Posté par . Évalué à 2.

          En fait, j'ai pas trouvé non plus, mais c'est p-e parce que c'est pas comme ça qu'on ferait en Python.

          C'est une des raisons pour lesquelle je n'aime pas Python ( et Java également) : il complique les choses qui se font de façon simple et évidentes dans d'autres langages. J'avais dit à une époque que je trouvais que Python était un langage qui force à se répéter, et voici un exemple typique de ce que je voulais dire. Entendons-nous bien : je ne veux pas "cracher" sur Python. J'avais juste promis il y a quelques temps que je donnerais des raisons concrètes pour lesquelles je trouvais que Python force à se répéter, et en voilà un concret.

          est-ce que ça serait pas plus simple d'avoir un générateur de classes proxy spécialisées ?

          Plus simple, je ne pense pas , d'autant plus que je n'ai pas forcément la maîtrise des classes sous-jacentes .. a moins que je puisse définir dans mon proxy, et de façon automatique, une méthode correspondant à chaque méthode définie dans l'objet. Mais ai-je un moyen de savoir si les méthodes magiques sont implémentées ?

          Sinon je ferai un proxy spécialisé pour mon besoin immédiat (ou je ne ferai pas de proxy du tout). Je verrai bien. Mais je trouve ça gênant.

          • [^] # Re: quelques remarques

            Posté par . Évalué à 4.

            En farfouillant sur le net, j'ai trouvé des infos supplémentaires.

            cf le premier message de http://www.gossamer-threads.com/lists/python/dev/651981

            Si tu es en Python 2, tu peux t'en sortir simplement en ne dérivant pas ta classe de object (j'ai testé, ça marche).

            Sinon, j'ai aussi trouvé une recette pour python 3. Cette recette fonctionne un peu différemment parce qu'elle crée des proxy sur des objets instanciés, et pas sur des classes. En reprenant l'exemple, comme ça, chémoisamarche :

            c=Proxy(list())
            c.append(3,)
            print ("value : " + str(c[0]))

            Mais ai-je un moyen de savoir si les méthodes magiques sont implémentées ?

            oui, la fonction dir liste tous les attributs de ce qu'on lui passe en paramètres.

            C'est une des raisons pour lesquelles je n'aime pas Python ( et Java également) : il complique les choses qui se font de façon simple et évidentes dans d'autres langages.

            et vice et versa !

            J'avais dit à une époque que je trouvais que Python était un langage qui force à se répéter, et voici un exemple typique de ce que je voulais dire.

            ça ne correspond pas à mon expérience. C'est vrai qu'en étant confronté à des nouveaux problèmes, il m'a parfois fallu un peu de temps pour trouver comment faire, mais j'ai toujours réussi à atteindre un truc que je trouvais propre en Python.

            Entendons-nous bien : je ne veux pas "cracher" sur Python. J'avais juste promis il y a quelques temps que je donnerais des raisons concrètes pour lesquelles je trouvais que Python force à se répéter, et en voilà un concret.

            raison suivante stp (ou un autre exemple de répétition) :-)

            • [^] # Re: quelques remarques

              Posté par . Évalué à 2.

              Je vais regarder ça. En tout cas, merci pour ton aide.

            • [^] # Re: quelques remarques

              Posté par . Évalué à 2.

              Ca marche effectivement en ne dérivant pas ma classe de object. J'avais ajouté ça à un moment pour éviter les boucles infinies en appelant le parent dans la méthode initialize. En nettoyant mon code par rapport à ce que j'ai fait, ça fonctionne.

              Merci encore.

    • [^] # Re: quelques remarques

      Posté par . Évalué à 2.

      Désolé, j'ai oublié de répondre à ça :

      Attention, getattr est censé renvoyé la valeur d'un attribut. Ton implémentation actuelle renvoie une méthode qui renvoie la valeur de l'attribut pour _obj. Est-ce que tu ne voudrais pas plutôt faire ? :
      ```

      def __getattr__(self,name):
          return getattr(self.__dict__['_obj'],name)
      

      Je t'avouerais que j'ai testé des trucs dans tous les sens, et que comme je l'ai dit plus haut, certaines choses se sont greffées par dessus. Ce soir je ne sais plus trop ou j'en suis et il est fort probable que tu aies raison. Je testerai Lundi (et merci au passage pour cette correction - sinon je te dirai ce que je veux faire exactement).
      ```

Suivre le flux des commentaires

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