web-dev-qa-db-fra.com

Désactiver la mise en mémoire tampon de sortie

La mise en mémoire tampon de sortie est-elle activée par défaut dans l'interpréteur de Python pour sys.stdout?

Si la réponse est positive, quels sont les moyens de la désactiver?

Suggestions jusqu'à présent:

  1. Utilisez le commutateur de ligne de commande -u
  2. Envelopper sys.stdout dans un objet qui se vide après chaque écriture
  3. Définir PYTHONUNBUFFERED env var
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Existe-t-il un autre moyen de définir un indicateur global dans sys/sys.stdout par programme lors de l'exécution?

471
Eli Bendersky

De Magnus Lycka répond sur une liste de diffusion :

Vous pouvez ignorer la mise en mémoire tampon pour tout un processus python à l'aide de "python -u" (ou #!/Usr/bin/env python -u etc.) ou en définissant la variable d'environnement PYTHONUNBUFFERED.

Vous pouvez également remplacer sys.stdout par un autre flux tel que wrapper qui effectue un vidage après chaque appel.

class Unbuffered(object):
   def __init__(self, stream):
       self.stream = stream
   def write(self, data):
       self.stream.write(data)
       self.stream.flush()
   def writelines(self, datas):
       self.stream.writelines(datas)
       self.stream.flush()
   def __getattr__(self, attr):
       return getattr(self.stream, attr)

import sys
sys.stdout = Unbuffered(sys.stdout)
print 'Hello'
394
Seb

Je préférerais mettre ma réponse dans Comment vider la sortie de Python print? ou dans la fonction d'impression de Python qui vide le tampon lorsqu'elle est appelée? , mais comme ils ont été marqués comme des doublons de celui-ci (ce que je ne suis pas d'accord), je vais y répondre ici.

Depuis Python 3.3, print () supporte le mot-clé argument "flush" ( voir documentation ):

print('Hello World!', flush=True)
95
# reopen stdout file descriptor with write mode
# and 0 as the buffer size (unbuffered)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Crédits: "Sebastian", quelque part sur la liste de diffusion Python.

édition tierce

Non pris en charge dans les versions récentes de Python

74

Oui, ça l'est.

Vous pouvez le désactiver sur la ligne de commande avec le commutateur "-u".

Alternativement, vous pouvez appeler .flush () sur sys.stdout à chaque écriture (ou l'envelopper avec un objet le faisant automatiquement)

49
Brian

Cela concerne la réponse de Cristóvão D. Sousa, mais je ne peux pas encore commenter.

Une façon simple d’utiliser le mot clé flush de Python pour toujours avoir une sortie non tamponnée est la suivante:

import functools
print = functools.partial(print, flush=True)

par la suite, print effacera toujours directement la sortie (sauf flush=False est donné).

Notez que (a) cela ne répond que partiellement à la question car il ne redirige pas tout le résultat. Mais je suppose que print est le moyen le plus courant de créer une sortie vers stdout/stderr en python. Ces deux lignes couvrent donc probablement la plupart des cas d'utilisation.

Notez (b) que cela ne fonctionne que dans le module/script où vous l'avez défini. Cela peut être utile lors de l'écriture d'un module car cela ne gêne pas le sys.stdout.

Python 2 ne fournit pas l'argument flush, mais vous pouvez émuler une fonction Python 3-type print comme décrit ici https://stackoverflow.com/a/27991478/3734258 .

16
tim
def disable_stdout_buffering():
    # Appending to gc.garbage is a way to stop an object from being
    # destroyed.  If the old sys.stdout is ever collected, it will
    # close() stdout, which is not good.
    gc.garbage.append(sys.stdout)
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

# Then this will give output in the correct order:
disable_stdout_buffering()
print "hello"
subprocess.call(["echo", "bye"])

Si vous ne sauvegardez pas l'ancien fichier sys.stdout, disable_stdout_buffering () n'est pas idempotent et plusieurs appels entraîneront une erreur comme celle-ci:

Traceback (most recent call last):
  File "test/buffering.py", line 17, in <module>
    print "hello"
IOError: [Errno 9] Bad file descriptor
close failed: [Errno 9] Bad file descriptor

Une autre possibilité est:

def disable_stdout_buffering():
    fileno = sys.stdout.fileno()
    temp_fd = os.dup(fileno)
    sys.stdout.close()
    os.dup2(temp_fd, fileno)
    os.close(temp_fd)
    sys.stdout = os.fdopen(fileno, "w", 0)

(Ajouter à gc.garbage n'est pas une très bonne idée car c'est là que sont placés les cycles imprescriptibles, et vous voudrez peut-être vérifier pour ceux-là.)

14
Mark Seaborn

Ce qui suit fonctionne dans Python 2.6, 2.7 et 3.2:

import os
import sys
buf_arg = 0
if sys.version_info[0] == 3:
    os.environ['PYTHONUNBUFFERED'] = '1'
    buf_arg = 1
sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sys.stderr = os.fdopen(sys.stderr.fileno(), 'a+', buf_arg)
12
Gummbum

Oui, il est activé par défaut. Vous pouvez le désactiver en utilisant l'option -u sur la ligne de commande lorsque vous appelez python.

11
Nathan

Vous pouvez également exécuter Python avec l'utilitaire stdbuf :

stdbuf -oL python <script>

7
dyomas

Il est possible de remplacer niquementwrite méthode de sys.stdout par une méthode qui appelle flush. La méthode proposée est décrite ci-dessous.

def write_flush(args, w=stdout.write):
    w(args)
    stdout.flush()

La valeur par défaut de l'argument w conservera la référence de la méthode originale write. Aprèswrite_flush est défini, l'original write peut être remplacé.

stdout.write = write_flush

Le code suppose que stdout est importé de cette façon from sys import stdout.

4
Vasily E.

Vous pouvez également utiliser fcntl pour modifier les indicateurs de fichier à la volée.

fl = fcntl.fcntl(fd.fileno(), fcntl.F_GETFL)
fl |= os.O_SYNC # or os.O_DSYNC (if you don't care the file timestamp updates)
fcntl.fcntl(fd.fileno(), fcntl.F_SETFL, fl)
4
jimx

(J'ai posté un commentaire, mais il s'est égaré. Alors, encore une fois :)

  1. Comme je l'ai remarqué, CPython (au moins sous Linux) se comporte différemment selon la destination de la sortie. S'il passe à un terminal, la sortie est vidée après chaque '\n'
    S'il passe dans un processus/processus, il est mis en mémoire tampon et vous pouvez utiliser les solutions basées sur flush() ou l'option - recommandée ci-dessus.

  2. Légèrement lié à la mise en mémoire tampon de sortie:
    Si vous parcourez les lignes de l’entrée avec

    for line in sys.stdin:
    ...

ensuite, l’implémentation for dans CPython collectera l’entrée pendant un moment, puis exécutera le corps de la boucle pour un ensemble de lignes d’entrée. Si votre script est sur le point d'écrire une sortie pour chaque ligne d'entrée, cela peut ressembler à la mise en mémoire tampon de la sortie, mais il s'agit en réalité d'un traitement par lots. Par conséquent, aucune des techniques flush(), etc. ne vous aidera. Fait intéressant, vous n’avez pas ce comportement dans pypy. Pour éviter cela, vous pouvez utiliser

while True: line=sys.stdin.readline()
...

3
tzp

Vous pouvez créer un fichier non mis en mémoire tampon et affecter ce fichier à sys.stdout.

import sys 
myFile= open( "a.log", "w", 0 ) 
sys.stdout= myFile

Vous ne pouvez pas modifier comme par magie la sortie standard fournie par le système; car il est fourni à votre programme python par le système d'exploitation.

3
S.Lott

Dans Python 3, vous pouvez appliquer un patch-singe à la fonction d'impression afin de toujours envoyer flush = True:

_orig_print = print

def print(*args, **kwargs):
    _orig_print(*args, flush=True, **kwargs)

Comme indiqué dans un commentaire, vous pouvez simplifier ceci en liant le paramètre flush à une valeur, via functools.partial:

print = functools.partial(print, flush=True)
3
Oliver

La variante qui fonctionne sans plantage (au moins sur win32; python 2.7, ipython 0.12) a ensuite été appelée ultérieurement (plusieurs fois):

def DisOutBuffering():
    if sys.stdout.name == '<stdout>':
        sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

    if sys.stderr.name == '<stderr>':
        sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
3
Laimis

Une façon d'obtenir une sortie non tamponnée serait d'utiliser sys.stderr au lieu de sys.stdout ou simplement d'appeler sys.stdout.flush() pour forcer explicitement une écriture.

Vous pouvez facilement rediriger tout ce qui est imprimé en faisant:

import sys; sys.stdout = sys.stderr
print "Hello World!"

Ou pour rediriger uniquement une instruction print particulière:

print >>sys.stderr, "Hello World!"

Pour réinitialiser stdout, vous pouvez simplement faire:

sys.stdout = sys.__stdout__
2
stderr