Partage
  • Partager sur Facebook
  • Partager sur Twitter

Rediriger sys.stderr dans un widget.

Sujet résolu
    24 septembre 2018 à 14:43:30

    Bonjour,

    dans une optique de modularité, j’ai commencé à séparer mes programmes en deux parties :

    • Un *.py qui contient la fonction principale, appelable directement en ligne de commande avec les bons paramètres.
    • Un *_GUI.pyw qui fabrique une interface graphique permettant de définir les différents paramètres et qui lance *.py avec les paramètres sélectionnés.

    Si je lance le *.py, les erreurs s’affichent automatiquement dans l’interpréteur python et si j’ai besoin d’informations supplémentaires un simple print() suffit.

    En revanche, je perds ces informations lorsque je lance mon programme en graphique. J’ai donc créé un context manager pour rediriger sys.stdout et sys.stderr vers un widget tkinter.Text() :

    class StdoutRedirector():
        """Context manager to redirect sys.stdout to a Text widget."""
        
        def __init__(self, widget):
                    self._widget = widget
                    
        def write(self, string):
            """Redefinition of the sys.stdout.write() function"""
            with Unlock(self._widget):
                self._widget.insert('end', string)
    
            self._widget.see('end')
    
        def __enter__(self):
            self._save_out = sys.stdout
            self._save_err = sys.stderr
            sys.stdout = self
            sys.stderr = self # Don’t work, I don’t know why.
            
        def __exit__(self, type, value, traceback):
            sys.stdout = self._save_out
            sys.stderr = self._save_err

    « Unlock  » est simplement un autre context manager qui permet de réserver le widget à l’affichage. Je le met sur « state=normal » le temps de l’écriture, puis de nouveau sur « disabled » ensuite.

    Ainsi je peux appeler n’importe quelle fonction ainsi :

    with StdoutRedirector(text _widget):
        ma_super_fonction_qui_print_et_qui_plante()

    Il se trouve que les print() fonctionne bien, ils sont correctement redirigés vers mon widget.

    En revanche, si ma fonction plante, l’erreur ne s’affiche pas dans le widget comme je voudrais. J’en déduis que raise, contrairement à print(), ne fait pas appel a sys.stderr.write().

    Du coup il fait appel à quoi ? Qu’est-ce qui se cache derrière ?

    N’hésitez pas non plus si vous connaissez de meilleur manière de faire. Je suis encore débutant.

    Merci d’avance.

    EDIT : En fait les erreurs sont bien redirigées si elles sont levées à l’intérieur du context manager (dans la définition de la classe j’entends). Par contre celles à l’intérieur du with ne sont pas redirigées.


    -
    Edité par Megalo 24 septembre 2018 à 16:42:27

    • Partager sur Facebook
    • Partager sur Twitter
      24 septembre 2018 à 20:47:39

      N'oublie pas que sys.stdout (ou sys.stderr) sera appelée à peu près comme ça : sys.stdout("erreur !"). En prenant en compte l'affectation à self, voilà ce qui va arriver : self("erreur !"). Or ta classe n'est pas faite pour être utilisée de cette manière.

      sys.stdout = self.write
      sys.stderr = self.write
      
      # pour avoir : self.write("erreur !")



      • Partager sur Facebook
      • Partager sur Twitter

      typage structurel ftw

        24 septembre 2018 à 23:28:52

        Hmm,

        Je ne suis pas sûr de comprendre ta réponse.

        >>> import sys
        >>> sys.stdout.write("test")
        test4
        >>> sys.stdout("test")
        Traceback (most recent call last):
          File "<pyshell#3>", line 1, in <module>
            sys.stdout("test")
        TypeError: 'PseudoOutputFile' object is not callable
        >>> sys.stderr.write("test")
        test4
        >>> sys.stderr("test")
        Traceback (most recent call last):
          File "<pyshell#5>", line 1, in <module>
            sys.stderr("test")
        TypeError: 'PseudoOutputFile' object is not callable

        D’après ce petit test (tiens, c’est marrant ce “4” d’ailleurs, c’est quoi ?), c’est bien la fonction write() de sys.stderr et sys.stdout qui devrait être appelée. Ma classe à été prévue pour overwriter ce sys.stdout.write() et sys.stderr.write() par self.write(). Ce n’est pas ce qu’il faut faire ?

        En tout cas si je mets self.write à la place de self, j’ai une erreur « AttributeError: 'function' object has no attribute 'write' ». Ce qui, pour le coup, me parait logique.

        Ce qui me surprend, c’est que la redirection du stdout fonctionne systématiquement, et la redirection du stderr fonctionne dans la définition de la classe, mais pas dans le « with StdoutRedirector(widget): »

        J’ai essayé

        with StdoutRedirector(text_widget):
            print("test1")
            sys.stdout.write("test2")
            sys.stderr.write("test3")
            raise Exception("test4")

        test 1, 2 et 3 s’affiche bien dans mon widget. Par contre, l’exception est toujours affichée dans l’interpreteur python.


        -
        Edité par Megalo 24 septembre 2018 à 23:57:54

        • Partager sur Facebook
        • Partager sur Twitter
          25 septembre 2018 à 8:46:43

          (Ce 4 c’est parce que la fonction write renvoie le nombre de caractères qu’elle a écrit). Et tu as tout à fait raison, je me trompe lourdement dans ma réponse, pour écrire sur le flux des erreurs c’est bien sys.stderr.write(txt).

          sys.stdout.write = self.write
          # stdout est la sortie standard, c’est là qu’écrit print
          
          sys.stderr.write = self.write
          # stderr est la sortie des erreurs, c’est là que seront écrites les erreurs

          Encore désolé pour cette énorme ânerie.
          Sinon je pense que pour attraper les exceptions tu voudras plutôt remplacer sys.excepthook : lorsqu’une exception n’est pas gérée, cette fonction est appelée comme suit : sys.excepthook(type, value, traceback). (L’un des rôles de sys.excepthook est d’écrire sur sys.stderr).

          https://docs.python.org/3/library/sys.html

          On pourrait imaginer :

          def gerant(type, value, traceback):
          
              sys.stderr.write(f”oh une erreur {traceback}”)
          
          sys.excepthook = gerant


          Tu dis qu’accéder à self.write cause une erreur ? Pourtant ton objet à bien une méthode write. Dans l’erreur il est marqué ‘function’ object has .... mais self c’est un objet non ?

          -
          Edité par digammaF 25 septembre 2018 à 8:53:00

          • Partager sur Facebook
          • Partager sur Twitter

          typage structurel ftw

            25 septembre 2018 à 9:46:10

            Salut,

            J’ai eu une illumination hier soir.

            __enter__() et __exit__() correspondent respectivement aux blocks try:… finally:… Donc stderr est bien overwrité dans le try. Par contre si une exception est levée, on passe directement au __exit__(), dans lequel je rétabli le comportement par défaut, et seulement ensuite stderr.write() est appelée.

            C’est facilement vérifiable en rajoutant des print() au début et à la fin du __exit__(), ou en commentant la ligne « sys.stderr = self._save_err »

            Sinon, overwriter uniquement la fonction sys.stdout.write() au lieu de la classe sys.stdout fonctionne aussi, c’est peut-être effectivement un peu moins bourrin.

            Pour l’erreur, je voulais dire dans le cas ou j’écrivais  « sys.stdout = self.write », comme dans ton premier message. Du coups, la fonction appelée quand  je printais était sys.stdout.write(), soit self.write.write(). Donc l’erreur est totalement logique.

            Excepthook, je n’arrive pas à l’overwriter. C’est toujours son comportement par défaut qui est appelé.

            Il faut donc que j’arrive à récupérer le contenu des info printées en cas d’erreur pour les écrire avant de rétablir sys.stderr.write().

            EDIT :

            Done :

            class StdoutRedirector():
                """Context manager to redirect sys.stdout to a Text widget."""
                
                def __init__(self, widget):
                            self._widget = widget
                            
                def write(self, string):
                    """Redefinition of the sys.stdout.write() function"""
                    with Unlock(self._widget):
                        self._widget.insert('end', string)
            
                    self._widget.see('end')
            
                def __enter__(self):
                    self._save_out = sys.stdout.write
                    self._save_err = sys.stderr.write
                    
                    sys.stdout.write = self.write
                    sys.stderr.write = self.write
            
                    
                def __exit__(self, type, value, traceback):
                    if sys.exc_info() != (None, None, None):
                        sys.excepthook(*sys.exc_info())
                    sys.stdout.write = self._save_out
                    sys.stderr.write = self._save_err


            Du coup merci tout de même pour ta participation. Je passe en résolu.

            EDIT 2 : en fait non. Ça fonctionne si je lance ma fenêtre depuis idle. Par contre si je lance directement en cliquant sur le pyw, rien ne s’affiche lors de l’erreur.

            EDIT 3 : c’est bon. Il faut bien overwriter sys.stdout, et no sys.stdout.write() (de même sys.stderr).

            -
            Edité par Megalo 25 septembre 2018 à 10:30:37

            • Partager sur Facebook
            • Partager sur Twitter
              25 septembre 2018 à 18:55:11

              Bon, je ne sais pas si j’ai aidé ou embrouillé les choses, en tout cas content de savoir que c’est résolu.
              • Partager sur Facebook
              • Partager sur Twitter

              typage structurel ftw

                25 septembre 2018 à 20:25:53

                C’est l’intention qui compte ;-)

                Merci.

                • Partager sur Facebook
                • Partager sur Twitter

                Rediriger sys.stderr dans un widget.

                × 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