Forum Programmation.python sys.stdout.write() avec python3 : pas d'écriture tant que pas EOL ?

Posté par (page perso) . Licence CC by-sa
Tags : aucun
3
18
jan.
2013

Bonjour, je me fais la main sur python et j'ai un bug douteux avec python3.
Voici un code simple, une espèce de shell :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

prompt='prompt> '

while True:
    sys.stdout.write(prompt)
    line = sys.stdin.readline().split('\n', 1)[0]
    sys.stdout.write('read: "' + line + '"\n')

Voilà ce qui se passe lorsque je l'interprète avec python :

prompt> test
read: "test"

Le prompt est affiché, et mon curseur est placé après le prompt, ce qui montre que sys.stdout.write() a bien écrit sur la sortie standard avant que sys.stdin.readline() ne lise l'entrée standard.

Mais voilà, dès que je passe à python3, c'est le drame

Je change le shebang de mon petit script en

#!/usr/bin/env python3

et je relance mon script, et j'obtiens :

test
prompt> read: "test"

le prompt est affiché qu'après avoir écrit ma commande, c'est comme si sys.stdout.write() était interprété après sys.stdin.readline(). En fait c'est plus fin que ça, certains au fond lèveront la main pour demander « mais pourquoi retirer '\n' pour l'afficher ensuite ». Tout à fait, je fais ça dans cet exemple pour être certain que c'est moi qui demande son impression, et qu'il n'est implicite nulle-part.

En fait voilà ce qui semble se passer en python :

sys.stdout.write("un ")
# les caractères sont imprimés sur la sortie standard
# un
sys.stdout.write("deux ")
# les caractères sont imprimés sur la sortie standard
# deux
sys.stdout.write("trois ")
# les caractères sont imprimés sur la sortie standard
# trois
sys.stdout.write("\n")
# le caractère EOL est imprimé sur la sortie standard
# 
#

Et voilà ce qui semble se passer en python3 :

sys.stdout.write("un ")
# les caractères sont imprimés dans un buffer
sys.stdout.write("deux ")
# les caractères sont imprimés dans un buffer
sys.stdout.write("trois ")
# les caractères sont imprimés dans un buffer
sys.stdout.write("\n")
# le caractère EOL est imprimé dans un buffer, tout le buffer est imprimé sur la sortie standard
# un deux trois
#

J'obtiens le même comportement avec print en python3 :

print("un ", end='')
# rien à l'écran
print("deux ", end='')
# rien à l'écran
print("trois", end='')
# rien à l'écran
print("")
# tout s'affiche
# un deux trois
#

Est-ce normal ? Comment contourner cela ? Comment imprimer un caractère sur la sortie standard sans imprimer le caractère EOL, en python3 ?

Merci !

  • # Flush du buffer?

    Posté par . Évalué à 5.

    Essayes en vidant le buffer:
    sys.stdout.flush()

    C'est certainement pour des raisons de perfs qu'ils mettent maintenant un buffer. En effet l'impression de caractères à l'écran est très lente comparée à l'ajout d'un caractère dans un buffer.

    • [^] # Re: Flush du buffer?

      Posté par (page perso) . Évalué à 1. Dernière modification le 18/01/13 à 14:55.

      C'est pareil en perl:

       perl -e 'print "fromage"; sleep(1); print " pain\n";'
      
      

      fait sleep avant de printer. C'est l'autoflush (en perl on peut faire "$|=1" pour afficher au fur et à mesure).

      En python j'ai trouvé:

      http://docs.python.org/3/library/io.html#io.TextIOWrapper.line_buffering

      c'est sans doute la même chose.

      • [^] # Re: Flush du buffer?

        Posté par . Évalué à 1.

        D'après mes tests c'est pareil en Java ou C : si tu écrit beaucoup de lignes, tu n'as aucune garantie d'écriture, il faut un OEL ou un flush pour être sûr que c'est écrit.

        • [^] # Re: Flush du buffer?

          Posté par . Évalué à 1. Dernière modification le 20/01/13 à 09:27.

          En C tu utilises surement fprintf qui est bufferisé. Mais tu peux aussi faire du non bufferisé avec write()

          • [^] # Re: Flush du buffer?

            Posté par . Évalué à 3.

            Techniquement ce sont les FILE * qui sont bufferisés, sauf stderr.

            Il est possible de modifier leur comportement vis à vis des buffer avec les primitives setbuf, setbuffer, setvbuf et cie.

            Please do not feed the trolls

    • [^] # Re: Flush du buffer?

      Posté par (page perso) . Évalué à 2.

      Ah merci c'est bien ça, ça sentait vraiment fort le buffer ! Merci !

      ce commentaire est sous licence cc by 4 et précédentes

  • # Saloperie de buffer

    Posté par (page perso) . Évalué à 2.

    Ah, les buffers de sortie… Une vraie plaie, ces trucs violent allègrement le principe de moindre surprise : par défaut, ça ne fait pas ce à quoi on s'attend. Le pire, ce sont les buffers de sortie qui ne sont même pas automatiquement vidés quand le programme se termine, de quoi passer des heures à se demander pourquoi il n'a rien écrit.

    • [^] # Re: Saloperie de buffer

      Posté par (page perso) . Évalué à 3.

      C'est vrai que ça surprend (donc risque), mais d'un autre côté ici ça peut se comprendre un peu, parce que chez moi: (le premier avec autoflush et l'autre non)

         $ time perl -e '$|++;print "x" for 1..1000000; print "\n";'
         [plein de x]
         real 0m3.830s
         user 0m0.464s
         sys  0m1.470s
         $ time perl -e 'print "x" for 1..1000000; print "\n";'
         [plein de x]
         real 0m0.238s
         user 0m0.223s
         sys  0m0.011s
      
      

      donc la différence peut être quand même non négligeable, et comme la plupart du temps ça n'a pas de conséquences, on peut comprendre que ce soit le comportement par défaut.

      Ceci dit, dans l'exemple j'aurais pu créer la chaîne avec 1 000 000 de x directement et du coup les deux versions auraient été identiques, donc ce n'est pas vraiment un bon exemple.

    • [^] # Re: Saloperie de buffer

      Posté par (page perso) . Évalué à 2.

      Effectivement, principe de moindre surprise… c'est pas évident.

      Pour moi, naivement, stdout est un fichier, ils auraient pu nommer le buffer autrement non ?

      Bon après c'est stratégique, en remplaçant le fichier par le buffer, et en gardant le même nom, ça force les gens à passer aux buffers, ce qui n'est pas une mauvaise idée.

      Mais bon, pour moi, stdout est un fichier, pas un buffer vers le fichier. Je l'aurai appelé buffer_to_stdout ou je sais pas quoi !

      ce commentaire est sous licence cc by 4 et précédentes

  • # La doc était pourtant assez claire...

    Posté par . Évalué à 3.

    http://docs.python.org/2/library/stdtypes.html#file.write
    file.write(str)
    Write a string to the file. There is no return value. Due to buffering, the string may not actually show up in the file until the flush() or close() method is called.

    http://docs.python.org/dev/library/sys.html#sys.stdout
    stdout is used for the output of print() and expression statements and for the prompts of input();
    […]
    When interactive, standard streams are line-buffered. Otherwise, they are block-buffered like regular text files. You can override this value with the -u command-line option.

    To write or read binary data from/to the standard streams, use the underlying binary buffer. For example, to write bytes to stdout, use sys.stdout.buffer.write(b'abc').

Suivre le flux des commentaires

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