Disabilita il buffering di output


532

Il buffering dell'output è abilitato di default nell'interprete di Python per sys.stdout?

Se la risposta è positiva, quali sono tutti i modi per disabilitarla?

Suggerimenti finora:

  1. Utilizzare l' -uopzione della riga di comando
  2. Avvolgere sys.stdoutin un oggetto che arrossisce dopo ogni scrittura
  3. Set PYTHONUNBUFFEREDenv var
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

C'è un altro modo per impostare un flag globale in sys/ sys.stdoutprogrammaticamente durante l'esecuzione?


7
Per "stampa" in Python 3, vedi questa risposta .
Antti Haapala,

1
Penso che uno svantaggio -usia che non funzionerà per il bytecode compilato o per le app con un __main__.pyfile come punto di ingresso.
Akhan,

La logica completa di inizializzazione di CPython è qui: github.com/python/cpython/blob/v3.8.2/Python/…
Beni Cherniavsky-Paskin,

Risposte:


443

Da Magnus Lycka rispondi a una mailing list :

Puoi saltare il buffering per un intero processo di Python usando "python -u" (o #! / Usr / bin / env python -u etc) o impostando la variabile d'ambiente PYTHONUNBUFFERED.

Puoi anche sostituire sys.stdout con qualche altro flusso come wrapper che esegue un flush dopo ogni chiamata.

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'

71
Sys.stdout originale è ancora disponibile come sys .__ stdout__. Nel caso ne avessi bisogno =)
Antti Rasinen,

40
#!/usr/bin/env python -unon funziona !! vedi qui
wim

6
__getattr__solo per evitare l'eredità ?!
Vladimir Keleshev,

33
Alcune note per salvare alcuni mal di testa: Come ho notato, il buffering dell'output funziona in modo diverso a seconda che l'output vada a un tty o ad un altro processo / pipe. Se va a tty, viene scaricato dopo ogni \ n , ma in una pipe viene bufferizzato. In quest'ultimo caso è possibile utilizzare queste soluzioni di lavaggio. In Cpython (non in pypy !!!): Se si esegue l'iterazione dell'input con for line in sys.stdin: ... allora il ciclo for raccoglierà un numero di linee prima che il corpo del ciclo venga eseguito. Questo si comporterà come buffering, sebbene sia piuttosto batch. Invece, fallo mentre è vero: line = sys.stdin.readline ()
tzp

5
@tzp: è possibile utilizzare iter()al posto del whileciclo: for line in iter(pipe.readline, ''):. Non ne hai bisogno su Python 3 dove for line in pipe:cede il più presto possibile.
jfs,


77
# reopen stdout file descriptor with write mode
# and 0 as the buffer size (unbuffered)
import io, os, sys
try:
    # Python 3, open as binary, then wrap in a TextIOWrapper with write-through.
    sys.stdout = io.TextIOWrapper(open(sys.stdout.fileno(), 'wb', 0), write_through=True)
    # If flushing on newlines is sufficient, as of 3.7 you can instead just call:
    # sys.stdout.reconfigure(line_buffering=True)
except TypeError:
    # Python 2
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Riconoscimenti: "Sebastian", da qualche parte nella mailing list di Python.


In Python3 puoi semplicemente sovrascrivere il nome della funzione di stampa con una di colore. È un trucco sporco però!
meawoppl

16
@meawoppl: potresti passare il flush=Trueparametro per print()funzionare da Python 3.3.
jfs,

La modifica della risposta per mostrare la risposta non è valida nella recente versione di Python
Mike,

entrambi os.fdopen(sys.stdout.fileno(), 'wb', 0)(notare il bbinario) e flush=Truelavorare per me in 3.6.4. Tuttavia, se si utilizza il sottoprocesso per avviare un altro script, assicurarsi di aver specificato python3, se sono installate più istanze di Python.
not2qubit,

1
@ not2qubit: se lo usi finisci os.fdopen(sys.stdout.fileno(), 'wb', 0)con un oggetto file binario, non un TextIOflusso. Dovresti aggiungere TextIOWrappera al mix (assicurandoti di abilitare write_throughl'eliminazione di tutti i buffer o utilizzare line_buffering=Truesolo per svuotare le newline).
Martijn Pieters

55

Sì.

Puoi disabilitarlo dalla riga di comando con l'opzione "-u".

In alternativa, puoi chiamare .flush () su sys.stdout ad ogni scrittura (o avvolgerlo con un oggetto che lo fa automaticamente)


19

Ciò si riferisce alla risposta di Cristóvão D. Sousa, ma non ho ancora potuto commentare.

Un modo semplice di usare l' flushargomento keyword di Python 3 per avere sempre un output senza buffer è:

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

in seguito, la stampa annulla sempre direttamente l'output (tranne quando flush=Falseviene fornito).

Si noti, (a) che questo risponde alla domanda solo parzialmente in quanto non reindirizza tutto l'output. Ma immagino printsia il modo più comune per creare output in stdout/ stderrin Python, quindi queste 2 righe coprono probabilmente la maggior parte dei casi d'uso.

Si noti (b) che funziona solo nel modulo / script in cui è stato definito. Questo può essere utile quando si scrive un modulo in quanto non si scherza con sys.stdout.

Python 2 non fornisce l' flushargomento, ma è possibile emulare una printfunzione di tipo Python 3 come descritto qui https://stackoverflow.com/a/27991478/3734258 .


1
Solo che non c'è flushkwarg in python2.
o11c,

@ o11c, sì hai ragione. Ero sicuro di averlo provato ma in qualche modo ero apparentemente confuso (: ho modificato la mia risposta, spero che vada bene ora. Grazie!
tim

14
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"])

Senza salvare il vecchio sys.stdout, disable_stdout_buffering () non è idempotente e più chiamate comporteranno un errore come questo:

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

Un'altra possibilità è:

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)

(L'aggiunta a gc.garbage non è una buona idea perché è dove vengono inseriti i cicli non graditi, e potresti volerli verificare.)


2
Se il vecchio stdoutcontinua a vivere sys.__stdout__come alcuni hanno suggerito, la spazzatura non sarà necessaria, giusto? È un bel trucco però.
Thomas Ahle,

1
Come per la risposta di @ Federico, questo non funzionerà con Python 3, poiché genererà l'eccezione ValueError: can't have unbuffered text I/Odurante la chiamata print().
gbmhunter,

La tua "altra possibilità" sembra inizialmente la soluzione più solida, ma sfortunatamente soffre di una condizione di competizione nel caso in cui un altro thread chiama open () dopo il tuo sys.stdout.close () e prima del tuo os.dup2 (temp_fd, fileno ). L'ho scoperto quando ho provato a usare la tua tecnica in ThreadSanitizer, che fa esattamente questo. Il fallimento è reso più forte dal fatto che dup2 () fallisce con EBUSY quando corre con open () in quel modo; vedi stackoverflow.com/questions/23440216/…
Don Hatch,

13

I seguenti lavori in Python 2.6, 2.7 e 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)

Eseguilo due volte e si blocca su Windows :-)
Michael Clerx,

@MichaelClerx Mmm hmm, ricordati sempre di chiudere i tuoi file xD.

Python 3.5 su Raspbian 9 mi dà OSError: [Errno 29] Illegal seekper la lineasys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs,

12

Sì, è abilitato per impostazione predefinita. È possibile disabilitarlo utilizzando l'opzione -u sulla riga di comando quando si chiama python.


7

Puoi anche eseguire Python con l' utilità stdbuf :

stdbuf -oL python <script>


2
Il buffering di linea (come -oLabilitato) è ancora in buffering - vedere f / e stackoverflow.com/questions/58416853/… , chiedendo perché l' end=''output non viene più immediatamente visualizzato.
Charles Duffy,

Vero, ma il buffering di linea è il valore predefinito (con un tty), quindi ha senso scrivere il codice supponendo che l'output sia totalmente senza buffer - forse è meglio esplicitamente print(..., end='', flush=True)dove è importante? OTOH, quando più programmi scrivono contemporaneamente sullo stesso output, il compromesso tende a spostarsi dal vedere progressi immediati alla riduzione dei mix di output e il buffering di linea diventa attraente. Quindi forse è meglio non scrivere esplicitamente flushe controllare il buffering esternamente?
Beni Cherniavsky-Paskin

Penso che nessuno. Il processo stesso dovrebbe decidere quando e perché chiama flush. Il controllo del buffering esterno è costretto a risolvere il problema qui
dyomas

7

In Python 3, è possibile eseguire il patch di scimmia della funzione di stampa, per inviare sempre flush = True:

_orig_print = print

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

Come sottolineato in un commento, puoi semplificarlo vincolando il parametro flush a un valore, tramite functools.partial:

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

3
Mi chiedo, ma non sarebbe un caso d'uso perfetto per functools.partial?
0xC0000022L

Grazie @ 0xC0000022L, questo lo rende migliore! print = functools.partial(print, flush=True)funziona bene per me.
MarSoft,

@ 0xC0000022L infatti, ho aggiornato il post per mostrare quell'opzione, grazie per averlo sottolineato
Oliver

3
Se vuoi che si applichi ovunque,import builtins; builtins.print = partial(print, flush=True)
Perkins,

4

Puoi anche usare fcntl per cambiare i flag di file in volo.

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)

1
Esiste un equivalente di Windows: stackoverflow.com/questions/881696/…
Tobu

12
O_SYNC non ha assolutamente nulla a che fare con il buffering a livello di spazio utente di questa domanda.
apenwarr,

4

È possibile sovrascrivere solo il write metodo di sys.stdoutcon uno che chiama flush. L'implementazione del metodo suggerito è di seguito.

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

Il valore predefinito wdell'argomento manterrà il writeriferimento al metodo originale . Dopo aver write_flush definito, l'originale writepotrebbe essere ignorato.

stdout.write = write_flush

Il codice presuppone che stdoutsia importato in questo modo from sys import stdout.


3

È possibile creare un file senza buffer e assegnare questo file a sys.stdout.

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

Non è possibile modificare magicamente lo stdout fornito dal sistema; poiché viene fornito al tuo programma python dal sistema operativo.


3

Variante che funziona senza crash (almeno su win32; python 2.7, ipython 0.12) quindi chiamata successivamente (più volte):

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)

Sei sicuro che questo non sia bufferizzato?
quantum

1
Dovresti cercare sys.stdout is sys.__stdout__invece di fare affidamento sull'oggetto sostitutivo con un attributo name?
Leewz,

funziona alla grande se Gunicorn non rispetta PYTHONUNBUFFERED per qualche motivo.
Brian Arsuaga,

3

(Ho pubblicato un commento, ma si è perso in qualche modo. Quindi, ancora :)

  1. Come ho notato, CPython (almeno su Linux) si comporta diversamente a seconda di dove va l'output. Se va a tty, l'output viene scaricato dopo ogni ' \n'
    Se va a una pipe / processo, viene bufferizzato e puoi usare le flush()soluzioni basate o l' opzione -u raccomandata sopra.

  2. Leggermente correlato al buffering dell'output:
    se si scorre le righe nell'input con

    for line in sys.stdin:
    ...

quindi l' implementazione for in CPython raccoglierà l'input per un po 'e quindi eseguirà il corpo del ciclo per un gruppo di righe di input. Se lo script sta per scrivere l'output per ogni riga di input, potrebbe sembrare un buffering dell'output, ma in realtà è un batch, e quindi nessuna delle flush()tecniche, ecc. Lo aiuterà. È interessante notare che non hai questo comportamento in pypy . Per evitarlo, puoi usare

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


ecco il tuo commento . Potrebbe essere un bug nelle versioni precedenti di Python. Potresti fornire un codice di esempio? Qualcosa come for line in sys.stdinvs.for line in iter(sys.stdin.readline, "")
jfs

per la riga in sys.stdin: print ("Riga:" + riga); sys.stdout.flush ()
tzp

sembra il bug read-ahead . Dovrebbe succedere solo su Python 2 e se stdin è una pipe. Il codice nel mio commento precedente dimostra il problema ( for line in sys.stdinfornisce una risposta ritardata)
jfs

2

Un modo per ottenere un output senza buffer sarebbe usare sys.stderrinvece di sys.stdouto semplicemente chiamare sys.stdout.flush()per forzare esplicitamente una scrittura.

Puoi reindirizzare facilmente tutto ciò che è stato stampato facendo:

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

O per reindirizzare solo per una printdichiarazione particolare :

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

Per resettare stdout puoi semplicemente fare:

sys.stdout = sys.__stdout__

1
Questo potrebbe diventare molto confuso quando in seguito provi a catturare l'output usando il reindirizzamento standard e scopri che non stai catturando nulla! ps il tuo stdout è in grassetto e roba del genere.
spazio libero

1
Un grande avvertimento sulla stampa selettiva su stderr è che ciò fa apparire le linee fuori posto, quindi a meno che tu non abbia anche il timestamp questo potrebbe diventare molto confuso.
haridsv,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.