Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Exercice][Moyen-Avancé] Traçage de l'exécution

Allez ! un peu de courage !

    28 janvier 2011 à 23:45:19

    Amis zéros pythonniens, je vous salue !

    Étant donné que cela fait bien longtemps que l'on a pas vu un topic d'exo sur ce forum. Me voici aujourd'hui avec un petit sujet de difficulté intermédiaire (voire avancée), pour vous faire creuser vos méninges et éventuellement en découvrir plus sur le fonctionnement de votre langage préféré.

    Autant le dire tout de suite, cet exercice va surtout vous faire travailler sur les décorateurs.
    Pour ceux qui n'auraient pas encore apprivoisé ces sales bêtes, vous pourrez vous diriger vers le chapitre sur les décorateurs du tutoriel Python officiel, et, si vous voulez approfondir la notion de décoration, (parce qu'un peu de pub ne fait jamais de mal) vous trouverez un peu de lecture supplémentaire avec mon mini-tuto sur le pattern Decorator en Python.

    Objectif


    L'objectif de cet exercice est de créer un décorateur qui va vous aider à visualiser ce qui se passe à l'exécution de votre programme. Grosso-modo, il va servir à afficher le déroulement du programme en termes d'appels de fonctions.

    Ceci peut s'avérer être une aide visuelle extrêmement utile lorsque vous maintenez un gros programme et que vous avez du mal, au bout d'un moment, à visualiser ce qui se passe réellement à l'intérieur, simplement en lisant le code. ;)

    Exemple


    Imaginons le petit programme suivant (qui n'a strictement aucun intérêt en lui-même, m'enfin...), comprenant des appels de fonctions imbriqués.

    def addition(a, b):
        return a + b
    
    def fonction1(a, b):
        c = addition(a, b)
        return a * b + c
    
    def fonction2(a, b):
        c = addition(a, b)
        d = fonction1(a, b)
        return addition(c, d)
    
    fonction2(b=2, a=3)
    


    Le but ultime de cet exercice est de créer un décorateur que l'on pourrait appliquer à ces 3 fonctions pour que lorsque l'on exécute le programme, celui-ci décrive son propre déroulement dans la console, comme ceci :

    > fonction2(a=3, b=2)
      > addition(3, 2)
      < 5
      > fonction1(3, 2)
        > addition(3, 2)
        < 5
      < 11
      > addition(5, 11)
      < 16
    < 16


    Bien évidemment, il est absolument interdit de tricher en modifiant le corps des fonctions : la seule chose que vous avez le droit de faire est de leur appliquer un décorateur ! Rassurez-vous cependant, j'ai décomposé cet exercice en plusieurs étapes, de manière à ce que vous ne soyez pas perdus dès le départ. ;)

    Énoncé


    1/ Créez un décorateur qui affiche ">" avant d'exécuter la fonction décorée, puis "<" après son exécution.

    Exemple


    def decor(fonction):
        # ...
        # le code de votre décorateur ici
        pass
    
    @decor
    def addition(a, b):
        return a + b
    
    addition(2, b=3)
    


    Résultat :
    >
    <


    2/ Modifiez ce décorateur pour qu'il affiche le nom de la fonction appelée avant son exécution, puis son résultat après son exécution, comme ceci :

    > addition
    < 5


    Indice : regardez du côté de l'attribut __name__... Eh oui, une fonction est un objet ! Souvenez-vous-en, ça va vous servir plus tard.

    3/ Modifiez ce décorateur pour qu'il affiche les arguments de la fonction appelée.

    > addition(2, b=3)
    < 5


    Indice : vous n'êtes pas encore à l'aise avec la notation "*args, **kwargs" ? C'est le moment de vous y coller ! :p

    4/ Modifiez ce décorateur pour gérer l'indentation.

    Lorsqu'une fonction est appelée à l'intérieur d'une autre fonction, les informations de la fonction appelée sont indentées par rapport à celles de la fonction appelante.

    Indice : Il va falloir vous creuser un peu la tête, mais souvenez-vous qu'une fonction est un objet, qu'un décorateur est une fonction, et que Python accepte que l'on ajoute "à chaud" des attributs sur presque n'importe quel objet. ;)

    5/ Question subsidiaire : essayez maintenant de faire en sorte que votre décorateur prenne en argument la taille des indentations.

    Par exemple, que l'on puisse indenter 2 espaces par 2 espaces, 4 par 4, ... tout cela au choix :
    @decor(4) # l'indentation sera de 4 espaces
    def addition(a, b):
        return a + b
    


    Indice : "Yo dawg ! I heard u like function decorators, so I put a function decorator in ur function so u can return ur decorator while u decorate ur function ! :lol: "

    Si la question 5 est subsidiaire, c'est simplement parce que son intérêt est purement de vous faire créer un niveau de décoration plus élevé, "pour la beauté du geste". Nous y reviendrons lorsque l'on corrigera cet exercice, mais c'est loin d'être la méthode la plus "intelligente" (dans le sens "simple à utiliser") pour gérer la taille des indentations.

    Sur ce, je vous souhaîte un bon courage, et j'attends vos participations avec impatience !
    • Partager sur Facebook
    • Partager sur Twitter
    Zeste de Savoir, le site qui en a dans le citron !
      29 janvier 2011 à 0:22:38

      Voici ma participation - je me suis beaucoup inspiré de ton code, j'y serais arrivé seul je pense, mais ça m'aurait creusé la tête une bonne heure. :D
      from __future__ import print_function
      	
      def logger(indentation):
      	logger.indent = 0
      	def wrap(function):
      		def wrapped_function(*args, **kwargs):
      			indent = " " * 2 * logger.indent
      			strargs = ', '.join(
      			  [repr(arg) for arg in args]
      			+ ["{0}={1}".format(key, repr(val)) for key, val in kwargs.iteritems()]
      			)
      			print("{0}> {1}({2})".format(indent, function.__name__, strargs))
      			logger.indent += indentation
      			result = function(*args, **kwargs)
      			logger.indent -= indentation
      			print("{0}< {1}".format(indent, result))
      			return result
      		return wrapped_function
      	return wrap
      	
      LOGGER_INDENTATION = 2
      
      @logger(LOGGER_INDENTATION)
      def addition(a, b):
      	return a + b
      
      @logger(LOGGER_INDENTATION)
      def fonction1(a, b):
      	c = addition(a, b)
      	return a * b + c
      
      @logger(LOGGER_INDENTATION)
      def fonction2(a, b):
      	c = addition(a, b)
      	d = fonction1(a, b)
      	return addition(c, d)
      	
      fonction2(b=2, a=3)
      

      Résultat :
      > fonction2(a=3, b=2)
          > addition(3, 2)
          < 5
          > fonction1(3, 2)
              > addition(3, 2)
              < 5
          < 11
          > addition(5, 11)
          < 16
      < 16
      • Partager sur Facebook
      • Partager sur Twitter
        29 janvier 2011 à 0:39:23

        Citation : FMIS@Menace.

        ... mais ça m'aurait creusé la tête une bonne heure. :D



        Feignasse ! :D

        Sinon, questions supplémentaires : essaye ça sur une méthode d'un objet -> l'affichage est super-moche.

        1/ Arrange-toi pour que ça affiche dans ce cas un truc sous la forme : NomDeLaClasse.methode(arguments, sans, le, self)

        2/ Crée un décorateur de classe qui applique le décorateur de fonction que tu viens de faire à toutes les méthodes d'un coup.

        Ça t'apprendra à avoir la flemme ! :lol:
        • Partager sur Facebook
        • Partager sur Twitter
        Zeste de Savoir, le site qui en a dans le citron !
          29 janvier 2011 à 23:53:53

          Merci pour l'exo Nohar. :)

          Mais python fait mal au crâne tout de même.

          j'essaye de faire un truc. ;)
          • Partager sur Facebook
          • Partager sur Twitter
          Zeste de Savoir, le site qui en a dans le citron !
            30 janvier 2011 à 2:53:29

            Citation : GurneyH


            Mais python fait mal au crâne tout de même.



            Explique nous ça ;)
            • Partager sur Facebook
            • Partager sur Twitter
              30 janvier 2011 à 3:06:03

              Oui, candide a raison, j'ai oublié de préciser (ça me paraissait évident), que le but de poster ce sujet d'exo sur le forum, c'est de pouvoir vous aider et répondre à toutes les questions que vous pourriez vous poser.

              Il suffit de nous dire à quelle étape vous bloquez et ce que vous ne comprenez pas... on ne va ni vous juger, ni vous manger : les décorateurs sont une notion de Python difficile à maîtriser quand on n'a pas encore pris le coup.
              • Partager sur Facebook
              • Partager sur Twitter
              Zeste de Savoir, le site qui en a dans le citron !
                30 janvier 2011 à 9:43:50

                Citation : candide

                Citation : GurneyH


                Mais python fait mal au crâne tout de même.



                Explique nous ça ;)


                Disons, que le concept des décorateurs est loin d'être naturel pour moi.

                Même en lisant le tuto de Nohar, j'ai l'impression de passer à coté de quelque chose.

                Sinon, un début(j'avance doucement.) :D
                def decor(f):
                    def f1():
                        print '> %s' %f.__name__
                        print '<'
                        
                    return f1
                
                @decor
                def foo():
                    pass
                
                foo()
                

                Rien que l'étape suivante,(afficher le retour de la fonction), je coince, pour l'instant.
                Non, en réfléchissant un peu, ça passe.
                def decor(f):
                    def f1(*args, **named):
                        print '> %s (' %f.__name__,
                        for a in args:
                            print a,
                        for name, value in named.items():
                            print ', %s = %s' %(name, value),
                        print ')'
                        print '<', f(*args, **named)     
                
                    return f1
                     
                
                @decor
                def addition(a, b):
                    return a + b
                
                addition(3, b = 5)
                
                • Partager sur Facebook
                • Partager sur Twitter
                Zeste de Savoir, le site qui en a dans le citron !
                  30 janvier 2011 à 10:26:26

                  C'est un bon début, mais il y a juste un petit soucis.

                  La fonction f1 est sensée se substituer à ta fonction addition. Cela signifie qu'elle doit accepter les mêmes arguments, et retourner la même chose... ;)
                  • Partager sur Facebook
                  • Partager sur Twitter
                  Zeste de Savoir, le site qui en a dans le citron !
                    30 janvier 2011 à 10:34:13

                    Citation : Nohar


                    La fonction f1 est sensée se substituer à ta fonction addition. Cela signifie qu'elle doit accepter les mêmes arguments, et retourner la même chose... ;)


                    Désolé, mais je ne comprend pas. :-°

                    Si je teste, j'obtiens
                    > addition ( 3 , b = 5 )
                    < 8


                    Tu veux dire que f1 dois prendre exactement les mêmes paramètres que la fonction décorée?
                    Alors, pour afficher les arguments, je dois avoir une couche supplémentaire(je ne trouve pas le mot correct. :-° )?
                    • Partager sur Facebook
                    • Partager sur Twitter
                    Zeste de Savoir, le site qui en a dans le citron !
                      30 janvier 2011 à 10:46:08

                      Ce que je veux dire, c'est qu'avec ton code, en faisant ceci :

                      @decor
                      def addition(a, b):
                          return a + b
                      
                      res = addition(3, 5)
                      print "la somme de 3 et 5 est", res
                      


                      res va valoir None, parce que le décorateur, bien qu'il l'affiche lui-même, ne pense pas à transmettre le résultat de l'addition : il est un intermédiaire invisible entre ton code et le code de la fonction addition, il intercepte l'appel de fonction, rajoute un peu de comportement, mais tout ça en faisant au moins tout ce que fait la fonction qu'il décore. Là, en l'état, tu oublies de retourner le résultat de la fonction décorée, ce qui est ennuyeux.
                      • Partager sur Facebook
                      • Partager sur Twitter
                      Zeste de Savoir, le site qui en a dans le citron !
                        30 janvier 2011 à 10:51:28

                        Ah oui, j'ai oublié l'essentiel. :-°


                        def decor(f):
                            def f1(*args, **named):
                                print '> %s (' %f.__name__,
                                for a in args:
                                    print a,
                                for name, value in named.items():
                                    print ', %s = %s' %(name, value),
                                print ')'
                                print '<', f(*args, **named)     
                                return f(*args, **named)
                            return f1
                             
                        
                        @decor
                        def addition(a, b):
                            return a + b
                        
                        res = addition(3, 5)
                        print "la somme de 3 et 5 est", res
                        


                        edit:
                        <code type="python"></code>
                        • Partager sur Facebook
                        • Partager sur Twitter
                        Zeste de Savoir, le site qui en a dans le citron !
                          30 janvier 2011 à 10:56:48

                          salut,
                          j'ai pas pigé grand chose, en fait ce qui m'arrête c'est que je n'arrive pas à comprendre l'utilité même des décorateurs. A part peut-être pour les class singleton ...

                          Même là encore, dans l'exemple donné (http://www.siteduzero.com/tutoriel-3-323958-les-decorateurs.html#ss_part_3) je ne comprend pas comment la variable "instances" survit à chaque instanciation ... ?
                          • Partager sur Facebook
                          • Partager sur Twitter
                            30 janvier 2011 à 11:30:07

                            @GurneyH : voilà ;)
                            Maintenant, tu arrives au plus casse-tête, l'indentation. ;)

                            @josmiley :
                            L'intérêt de créer un décorateur en Python (enfin, un décorateur statique avec la syntaxe @truc, c'est qu'il te permet de rajouter un comportement ou une caractéristique à une fonction ou une classe sans en modifier le code-source.

                            Par exemple dans cet exercice, si tu maintiens un gros programme dont l'exécution est complexe et que tu dois le débugger, tu peux vouloir afficher ce qui se passe réellement pendant son exécution, temporairement, le temps de comprendre : au lieu de modifier toutes tes fonction pour y rajouter des print dans tous les sens (que tu risques d'oublier par la suite), tu peux simplement leur coller un décorateur au tout début de leur définition, ce qui va te permettre de faire la même chose, très rapidement, facilement et proprement : c'est une aide précieuse dans le sens où le comportement que tu rajoutes ne change pas les fonctions en profondeur (elles feront toujours le même travail), et que le code source reste très lisible (quand tu veux retirer tes décorateurs, une simple recherche dans le document sur "@nom_du_decorateur" d'indique tous les endroits où tu l'as appliqué, ce qui est plus simple qu'une recherche sur "print", par exemple).

                            Dans beaucoup de cas, il s'agit de sucre syntaxique, mais on peut imaginer, par exemple, que tu veuilles créer un module qui ne va pas forcément effectuer un travail "métier" sur le programme, mais plus adapter les fonctions existantes du programme à un fonctionnement précis.

                            On pourrait imaginer, par exemple (je n'y ai pas réfléchi sérieusement, mais ça doit pouvoir se faire), que tu veuilles optimiser en vitesse l'exécution d'un programme en faisant en sorte que certaines fonctions s'exécutent dans un thread séparé du reste du programme mais que tu ne sais pas encore précisément quelles fonctions valent la peine d'être threadées. Dans ce cas, on peut imaginer que l'on crée un décorateur @threadme qui va prendre ta fonction et faire en sorte qu'elle s'exécute dans un thread à part. Ce décorateur effectuerait toutes les opérations "casserole" (le boiler-plate, c'est tout le code rébarbatif qu'il faut taper pour y arriver), ainsi, tu pourrais faire tes essais en appliquant ton décorateur @threadme à différentes fonctions et constater le résultat. Au lieu d'avoir à réécrire et ré-effacer une dizaine de lignes à chaque fois, tu n'as plus besoin de se faire ballader qu'une seule instruction.
                            Toujours dans ce petit exemple : tu veux optimiser en vitesse, dans ce cas, tu veux examiner le temps que prennent plusieurs de tes fonctions à s'exécuter en fonction de tes modifications (sauf qu'à la fin, quand tu livres le programme, dans son utilisation normale, tu n'as pas du tout besoin de tout le code qui sert à chronométrer), pourquoi pas créer un décorateur @time_me qui fait ce boulot tout seul ? Ça t'évite de rajouter dans ta fonction les variables qui vont bien et le calcul du temps d'exécution ainsi que l'affichage dans la console... tout ceci est géré par le décorateur, et peut être appliqué à toutes les fonctions que tu veux, sans alourdir ton code. ;)

                            J'espère t'avoir convaincu de leur intérêt.

                            Pour ce qui est du singleton, c'est simplement parce que la variable "instances" est un "membre de classe" et non un attribut : la variable est attachée à une classe (la classe Singleton) et non à une instance comme le sont les attributs (self.truc). Il existe donc autant de variables instances que de classes Singleton : à savoir une et une seule.
                            • Partager sur Facebook
                            • Partager sur Twitter
                            Zeste de Savoir, le site qui en a dans le citron !
                              3 février 2011 à 10:11:43

                              Salut NoHar,

                              Est-ce que tu pourrais me dire (rapidement) quelles sont les notions à connaître pour aborder ton exercices sur les décorateurs en python? Comme ça je peux les rajouter dans le tableau du topic exercices. :)
                              N'ayant pas un tel niveau en python, je ne saurai pas trop dire par moi même quelles sont ces notions... :-°

                              Merci!
                              • Partager sur Facebook
                              • Partager sur Twitter
                                3 février 2011 à 13:00:06

                                Citation : Fort en pommes

                                Est-ce que tu pourrais me dire (rapidement) quelles sont les notions à connaître pour aborder ton exercices sur les décorateurs en python?



                                Les décorateurs, les boucles for, les dictionnaires et les fonctions (et pas mal de réflexion).
                                • Partager sur Facebook
                                • Partager sur Twitter
                                Anonyme
                                  17 avril 2011 à 14:44:04

                                  Bonjour,

                                  Je me demandais dans le cadre de cet exercice, existe-t-il un moyen de redéfinir la classe 'function' de Python de telle sorte qu'à la création d'une fonction n'importe où dans le programme, on puisse décorer automatiquement cette fonction ?

                                  L'idée c'est de définir une sorte de métaclasse, mais pour des fonctions en fait.
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    18 avril 2011 à 4:29:43

                                    Je ne pense pas que ce soit possible...

                                    En revanche pour appliquer un décorateur à toutes les fonctions qui sont chargées, on peut faire un truc crade dans ce genre (python 3, mais fonctionne aussi en python 2) :

                                    def decor(fn):
                                        def wrap(*args):
                                            r = fn(*args)
                                            print("{0} called:".format(fn.__name__), r)
                                            return r
                                        return wrap
                                    
                                    def fct1(a, b):
                                        return a + b
                                    
                                    def fct2(a, b):
                                        return a - b
                                    
                                    
                                    def decorate_all(decorator):
                                        d = globals()
                                        for key, elt in d.items():
                                            if type(elt) == type(decorator) and elt != decorator:
                                                d[key] = decor(elt)
                                    
                                    print(fct1(3, 2))
                                    decorate_all(decor)
                                    print(fct1(3, 2))
                                    


                                    5
                                    fct1 called: 5
                                    5
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Zeste de Savoir, le site qui en a dans le citron !
                                      18 avril 2011 à 20:40:07

                                      Citation : GurneyH

                                      Ah oui, j'ai oublié l'essentiel. :-°


                                      def decor(f):
                                          def f1(*args, **named):
                                              print '> %s (' %f.__name__,
                                              for a in args:
                                                  print a,
                                              for name, value in named.items():
                                                  print ', %s = %s' %(name, value),
                                              print ')'
                                              print '<', f(*args, **named)     
                                              return f(*args, **named)
                                          return f1
                                           
                                      
                                      @decor
                                      def addition(a, b):
                                          return a + b
                                      
                                      res = addition(3, 5)
                                      print "la somme de 3 et 5 est", res
                                      



                                      edit:
                                      <code type="python"></code>



                                      Je crois qu'il y a un problème: dans ton code, tu appelle plusieurs fois la fonction décoré.
                                      En gros tu considère qu'une fonction retournera toujours la même chose si on lui passe les même arguments, ce qui est faux! :pirate: ( pense au module random).
                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        29 septembre 2013 à 20:42:21

                                        J'ai quasiment trouvé

                                        IDENTATION = 0
                                        
                                        def decor(nb=4):
                                            def decorateur(fonction):
                                                def fonction_nouvelle(*arg, **kwarg):
                                                    global IDENTATION
                                                    IDENTATION += 4
                                                    print(" " * IDENTATION, ">", fonction.__name__, arg, kwarg)
                                                    resultat = fonction(*arg,**kwarg)
                                                    print(" " * IDENTATION,"<", resultat)
                                                    IDENTATION -= 4
                                                    return resultat
                                                return fonction_nouvelle
                                            return decorateur
                                        
                                        @decor(4)
                                        def addition(a,b):
                                            return a + b
                                        
                                        @decor(4)
                                        def f1(a,b):
                                            c = addition(a,b)
                                            return a * c
                                        
                                        @decor(4)
                                        def f2(a,b):
                                            e = f1(a,b)
                                            return e

                                        j'ai un probleme de portée de variable avec IDENTATION (cela explique l'assignement au debut). pour afficher les parametres il y a quuelque chose de speciale a faire ?

                                        -
                                        Edité par Smich74 29 septembre 2013 à 20:43:06

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                        Si c'était facile, tout le monde le ferait.
                                          29 septembre 2013 à 23:36:47

                                          Ta fonction decor est un objet, donc tu peux lui assigner des attributs.

                                          C'est une façon plutôt astucieuse de créer une variable statique sans utiliser le mot-clé global. ;)

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                          Zeste de Savoir, le site qui en a dans le citron !
                                            30 septembre 2013 à 7:22:27

                                            ah bah oui je suis con :/

                                             je vais reparer cet affront :)

                                            def decor(nb=4):
                                                decor.identation = -nb
                                                def decorateur(fonction):
                                                    def fonction_nouvelle(*arg, **kwarg):
                                                        decor.identation += 4
                                                        print(" " * decor.identation, ">", fonction.__name__, arg, kwarg)
                                                        resultat = fonction(*arg,**kwarg)
                                                        print(" " * decor.identation,"<", resultat)
                                                        decor.identation -= 4
                                                        return resultat
                                                    return fonction_nouvelle
                                                return decorateur
                                            
                                            @decor(4)
                                            def addition(a,b):
                                                return a + b
                                            
                                            @decor(4)
                                            def f1(a,b):
                                                c = addition(a,b)
                                                return a * c
                                            
                                            @decor(4)
                                            def f2(a,b):
                                                e = f1(a,b)
                                                return e



                                            -
                                            Edité par Smich74 30 septembre 2013 à 17:28:01

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                            Si c'était facile, tout le monde le ferait.

                                            [Exercice][Moyen-Avancé] Traçage de l'exécution

                                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                            • Editeur
                                            • Markdown