10.2. Entrée, sortie et erreur standard

Les utilisateurs d'UNIX sont déjà familiers avec les concepts d'entrée standard, de sortie standard et d'erreur standard. Cette section s'adresse aux autres.

La sortie standard et l'erreur standard (communément abrégé en stdout et en stderr) sont des canaux de communication (pipes) intégrés à chaque système UNIX. Lorsque vous affichez (fonction print) quelque chose, il est dirigé vers le canal de communication stdout; quand votre programme plante et affiche des informations de débogage (comme un traceback en Python), elles sont envoyées vers le canal de communication stderr. Chacun de ces deux canaux sont d'ordinaire simplement connectés à la fenêtre du terminal avec laquelle vous travaillez et de cette façon vous voyez s'afficher la sortie du programme ou l'information de débogage s'il plante. (Si vous travaillez sur un système pourvu d'un IDE Python fenêtré, stdout et stderr sont redirigés par défaut vers la «Fenêtre Interactive».)

Exemple 10.8. Introduction à stdout et à stderr

>>> for i in range(3):
...     print 'Dive in'             1
Dive in
Dive in
Dive in
>>> import sys
>>> for i in range(3):
...     sys.stdout.write('Dive in') 2
Dive inDive inDive in
>>> for i in range(3):
...     sys.stderr.write('Dive in') 3
Dive inDive inDive in
1 Comme vous l'avez vu dans l'Exemple 6.9, «Compteurs simples», vous pouvez utiliser la fonction prédéfinie de Python range pour construire un simple compteur de boucles qui répète une instruction un nombre déterminé de fois.
2 stdout est un pseudo objet-fichier; appeler sa fonction write affichera toutes les chaînes que vous lui donnez. En fait, c'est bien ce que fait la fonction print; elle ajoute un retour chariot à la fin de la chaîne que vous affichez et appelle sys.stdout.write.
3 Dans le cas le plus simple, stdout et stderr envoient leur sortie au même endroit : l'IDE Python (si vous en utilisez un), ou la console (si vous avez lancé Python à partir de la ligne de commande). Comme stdout, stderr n'ajoute pas de retour chariot pour vous; si vous en avez besoin, ajoutez-les vous-même.

stdout et stderr sont toutes les deux des pseudo objet-fichiers, comme ceux dont il a été question dans la Section 10.1, «Extraire les sources de données en entrée», mais ils sont tous les deux en écriture seule. Ils n'ont pas de méthode read, seulement une méthode write. Ils n'en restent pas moins des pseudo objet-fichiers auxquels vous pouvez assigner n'importe quel autre fichier - ou pseudo objet-fichier afin d'en rediriger la sortie.

Exemple 10.9. Rediriger la sortie standard

[you@localhost kgp]$ python stdout.py
Dive in
[you@localhost kgp]$ cat out.log
This message will be logged instead of displayed

(Avec Windows, il faut utiliser type au lieu de cat pour afficher le contenu d'un fichier.)

Si vous ne l’avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.

#stdout.py
import sys

print 'Dive in'                                          1
saveout = sys.stdout                                     2
fsock = open('out.log', 'w')                             3
sys.stdout = fsock                                       4
print 'This message will be logged instead of displayed' 5
sys.stdout = saveout                                     6
fsock.close()                                            7
1 Ce message s'affichera dans la «Fenêtre Interactive» de l'IDE (ou sur la console, si le script est lancé à la ligne de commande).
2 Sauvegarder toujours stdout avant de le rediriger, ainsi vous pourrez revenir à la normale plus tard.
3 Ouvre un nouveau fichier en écriture. Si le fichier n'existe pas, il sera créé. Si le fichier existe, il sera écrasé
4 Redirige toutes les sorties supplémentaires dans le fichier que vous venez d'ouvrir.
5 Ce message «s'affichera» seulement dans le fichier journal; il ne sera pas visible ni dans la fenêtre de l'IDE ni à l'écran.
6 Restaure stdout dans l'état où il était avant que vous n'y mettiez la pagaïe.
7 Ferme fichier journal.

Rediriger stderr fonctionne exactement de la même manière, en utilisant sys.stderr au lieu de sys.stdout.

Exemple 10.10. Rediriger un message d'erreur

[you@localhost kgp]$ python stderr.py
[you@localhost kgp]$ cat error.log
Traceback (most recent line last):
  File "stderr.py", line 5, in ?
    raise Exception, 'this error will be logged'
Exception: this error will be logged

Si vous ne l’avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.

#stderr.py
import sys

fsock = open('error.log', 'w')               1
sys.stderr = fsock                           2
raise Exception, 'this error will be logged' 3 4
1 Ouvre le fichier journal où vous voulez enregistrer l'information de débogage.
2 Redirige l'erreur standard en affectant à stderr l'objet-fichier correspondant au fichier journal nouvellement créé.
3 Déclenche une exception. Notez que sur l'écran de sortie rien ne s'affiche. Toute l'information de traceback a été écrite dans error.log.
4 Remarquez également que vous ne fermez pas explicitement votre fichier journal, ni ne restaurez stderr dans son état d'origine. Il n'y a pas d'erreur, puisqu'une fois le programme planté (à cause de l'exception), Python nettoiera et fermera le fichier pour nous et cela n'a pas d'importance que stderr soit restauré, puisque, comme je l'ai signalé, le programme plante et Python se termine. Un retour à l'état antérieur est plus important pour stdout, si vous souhaiter continuer à travailler avec le même script ultérieurement.

Puisqu'il est si trivial d'écrire des messages d'erreurs sur le canal d'erreur standard, il existe une syntaxe abrégée qui peut être utilisée plutôt que de s'embêter à effectuer une redirection complète.

Exemple 10.11. Afficher un message sur stderr

>>> print 'entering function'
entering function
>>> import sys
>>> print >> sys.stderr, 'entering function' 1
entering function
1 Cette syntaxe abrégée de l'expression print peut être utilisée pour écrire dans tout fichier ou pseudo objet-fichier. Dans cet exemple, vous pouvez rediriger une seule expression print vers stderr sans affecter les expressions print ultérieures.

L'entrée standard, de l'autre côté, est un objet-fichier en lecture seule et représente les données circulant entre un programme et un programme exécuté antérieurement. Cela n'a probablement pas grand sens pour les utilisateurs chevronnés de Mac OS, ou même pour les utilisateurs de Windows à moins que vous ne soyez coutumier de la ligne de commande MS-DOS. Son principe de fonctionnement vous permet de construire une chaîne de commandes sur une seule ligne, de telle sorte que la sortie d'un premier programme devienne l'entrée du programme suivant dans la chaîne. Le premier programme retourne simplement un résultat vers la sortie standard (sans effectuer lui-même une redirection spéciale, sinon le renvoi en sortie de quelques instructions print ou que sais-je encore), le programme suivant lit l'entrée standard, et le système d'exploitation se charge de connecter la sortie d'un programme à l'entrée du programme suivant.

Exemple 10.12. Chaîner les commandes

[you@localhost kgp]$ python kgp.py -g binary.xml         1
01100111
[you@localhost kgp]$ cat binary.xml                      2
<?xml version="1.0"?>
<!DOCTYPE grammar PUBLIC "-//diveintopython.org//DTD Kant Generator Pro v1.0//EN" "kgp.dtd">
<grammar>
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar>
[you@localhost kgp]$ cat binary.xml | python kgp.py -g - 3 4
10110001
1 Comme vous l'aviez vu dans la Section 9.1, «Plonger», cette commande affiche une chaîne de huit bits aléatoires, 0 ou 1.
2 Cette commande affiche simplement la totalité du contenu de binary.xml. (les utilisateurs de Windows doivent utiliser type au lieu de cat.)
3 Cette commande affiche le contenu de binary.xml, mais le caractère «|», appelé «pipe», signifie que le contenu ne sera pas affiché à l'écran. A la place, il deviendra l'entrée standard de la prochaine commande qui dans ce cas appelle votre script Python.
4 Plutôt que de spécifier un module (comme binary.xml), vous spécifiez «-», ce qui oblige votre script à charger la grammaire à partir de l'entrée standard au lieu d'un fichier particulier sur le disque. (Vous en saurez plus à ce propos dans le prochain exemple.) Ainsi le résultat est le même qu'avec la syntaxe précédente, où vous spécifiiez directement le nom du fichier de grammaire, mais pensez en plus aux nombreuses possibilités qui s'offrent à vous. Plutôt que de simplement exécuter cat binary.xml, vous pourriez lancer un premier script qui générerait dynamiquement une grammaire que vous redirigeriez vers votre script. Les données pourraient provenir de n'importe où : une base de données, un méta-script générateur de grammaire, ou que sais-je encore. L'important est que vous n'avez pas besoin de modifier votre script kgp.py pour tenir compte de cette fonctionnalité. Tout ce dont vous avez besoin, c'est de pouvoir récupérer le fichier de grammaire à partir de l'entrée standard et alors vous pouvez confier toute la logique restante à un autre programme.

Comment donc notre script «sait»-il qu'il doit lire à partir de l'entrée standard quand le fichier de grammaire correspond à «-» ? Cela n'a rien de magique; juste logique.

Exemple 10.13. Lire à partir de l'entrée standard dans kgp.py


def openAnything(source):
    if source == "-":    1
        import sys
        return sys.stdin

    # try to open with urllib (if source is http, ftp, or file URL)
    import urllib
    try:

[... snip ...]
1 Il s'agit de la fonction openAnything de toolbox.py, que vous aviez précédemment examinée dans la Section 10.1, «Extraire les sources de données en entrée». Tout ce que vous avez fait est d'ajouter trois lignes de code au début de cette fonction pour tester si la source correspond à «-»; si c'est le cas, vous retournez sys.stdin. Rien de plus ! Souvenez-vous que stdin est un pseudo objet-fichier pourvu d'une méthode read, si bien que le reste du code (dans kgp.py où vous appelez openAnything) ne change pas d'un pouce.