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.
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 !")
>>> 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.
(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).
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 ?
__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).
× 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.
typage structurel ftw
typage structurel ftw
typage structurel ftw