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:
-u
sys.stdout
dans un objet qui se vide après chaque écriturePYTHONUNBUFFERED
env varsys.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?
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'
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)
# 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
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)
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 .
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à.)
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)
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.
Vous pouvez également exécuter Python avec l'utilitaire stdbuf :
stdbuf -oL python <script>
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
.
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)
(J'ai posté un commentaire, mais il s'est égaré. Alors, encore une fois :)
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.
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()
...
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.
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)
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)
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__