Desativar buffer de saída

532

O buffer de saída está ativado por padrão no intérprete do Python para sys.stdout?

Se a resposta for positiva, quais são as formas de desativá-la?

Sugestões até agora:

  1. Use a -uopção de linha de comando
  2. Envolver sys.stdoutum objeto que libere após cada gravação
  3. Definir PYTHONUNBUFFEREDenv var
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Existe alguma outra maneira de definir algum sinalizador global sys/ sys.stdoutprogramaticamente durante a execução?

Eli Bendersky
fonte
7
Para `print 'no Python 3, veja esta resposta .
Antti Haapala 16/10
1
Eu acho que uma desvantagem -ué que ele não funcionará para código de código compilado ou para aplicativos com um __main__.pyarquivo como ponto de entrada.
Akhan
A lógica completa de inicialização do CPython está aqui: github.com/python/cpython/blob/v3.8.2/Python/…
Beni Cherniavsky-Paskin

Respostas:

443

De Magnus Lycka, responda em uma lista de discussão :

Você pode pular o buffer para um processo python inteiro usando "python -u" (ou #! / Usr / bin / env python -u etc) ou configurando a variável de ambiente PYTHONUNBUFFERED.

Você também pode substituir o sys.stdout por algum outro fluxo, como o wrapper, que executa um flush após cada chamada.

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'
Seb
fonte
71
O sys.stdout original ainda está disponível como sys .__ stdout__. Apenas no caso de você precisar =)
Antti Rasinen 20/09/08
40
#!/usr/bin/env python -unão funciona !! veja aqui
wim
6
__getattr__só para evitar herança ?!
precisa saber é o seguinte
32
Algumas notas para evitar algumas dores de cabeça: Como observei, o buffer de saída funciona de maneira diferente dependendo se a saída vai para um tty ou outro processo / canal. Se for para um tty, será liberado após cada \ n , mas em um pipe será armazenado em buffer. Neste último caso, você pode fazer uso dessas soluções de descarga. No Cpython (não no pypy !!!): Se você iterar sobre a entrada com a linha for em sys.stdin: ... o loop for coletará um número de linhas antes que o corpo do loop seja executado. Isso se comportará como buffer, embora seja bastante em lotes. Em vez disso, faça while true: line = sys.stdin.readline ()
tzp
5
@tzp: você poderia usar iter()em vez do whileloop: for line in iter(pipe.readline, ''):. Você não precisa dele no Python 3, onde for line in pipe:produz o mais rápido possível.
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)

Créditos: "Sebastian", em algum lugar da lista de discussão em Python.

Federico A. Ramponi
fonte
No Python3, você pode simplesmente substituir o nome da função de impressão por uma que esteja liberada. É um truque sujo!
precisa saber é
16
@meawoppl: você pode passar o flush=Trueparâmetro para print()funcionar desde o Python 3.3.
JFS
Edição de resposta ao programa de resposta não é válido na versão recente do python
Mike
ambos os.fdopen(sys.stdout.fileno(), 'wb', 0)(observe o bpara binário) e flush=Truetrabalhem para mim em 3.6.4. No entanto, se você estiver usando o subprocesso para iniciar outro script, verifique se especificou python3se possui várias instâncias do python instaladas.
not2qubit
1
@ not2qubit: se você usar, os.fdopen(sys.stdout.fileno(), 'wb', 0)você acaba com um objeto de arquivo binário, não com um TextIOfluxo. Você precisaria adicionar um TextIOWrapperao mix (certifique-se de ativar write_througha eliminação de todos os buffers ou use line_buffering=Trueapenas para liberar novas linhas).
Martijn Pieters
55

Sim, ele é.

Você pode desativá-lo na linha de comando com a opção "-u".

Como alternativa, você pode chamar .flush () no sys.stdout em cada gravação (ou agrupá-lo com um objeto que faça isso automaticamente)

Brian
fonte
19

Isso está relacionado à resposta de Cristóvão D. Sousa, mas ainda não pude comentar.

Uma maneira direta de usar o flushargumento de palavra - chave do Python 3 para sempre ter saída sem buffer é:

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

depois, a impressão sempre liberará a saída diretamente (exceto se flush=Falsefor fornecido).

Observe que (a) isso responde apenas parcialmente à pergunta, pois não redireciona toda a saída. Mas eu acho que printé a maneira mais comum de criar saída para stdout/ stderrem python, portanto essas duas linhas cobrem provavelmente a maioria dos casos de uso.

Observe (b) que ele funciona apenas no módulo / script em que você o definiu. Isso pode ser bom ao escrever um módulo, pois não mexe com o sys.stdout.

O Python 2 não fornece o flushargumento, mas você pode emular uma printfunção do tipo Python 3, conforme descrito aqui https://stackoverflow.com/a/27991478/3734258 .

tim
fonte
1
Exceto que não há flushkwarg no python2.
o11c 5/05/19
@ o11c, sim, você está certo. Eu tinha certeza de que o testei, mas de alguma forma eu estava aparentemente confuso (: modifiquei minha resposta, espero que esteja tudo bem agora. Obrigado!
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"])

Sem salvar o sys.stdout antigo, disable_stdout_buffering () não é idempotente e várias chamadas resultarão em um erro como este:

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

Outra possibilidade é:

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)

(Anexar ao gc.garbage não é uma boa idéia, pois é onde os ciclos não-viáveis ​​são colocados, e você pode procurar por eles.)

Mark Seaborn
fonte
2
Se o velho stdoutainda vive sys.__stdout__como alguns sugeriram, a coisa do lixo não será necessária, certo? É um truque legal, no entanto.
Thomas Ahle
1
Como na resposta de @ Federico, isso não funcionará com o Python 3, pois lançará a exceção ValueError: can't have unbuffered text I/Oao chamar print().
Gbmhunter
Sua "outra possibilidade" parece a princípio a solução mais robusta, mas infelizmente sofre uma condição de corrida no caso em que outro thread chama open () após o seu sys.stdout.close () e antes do seu os.dup2 (temp_fd, fileno ) Descobri isso quando tentei usar sua técnica no ThreadSanitizer, que faz exatamente isso. A falha é aumentada pelo fato de que dup2 () falha com EBUSY quando corre com open () assim; Veja stackoverflow.com/questions/23440216/…
Don Hatch
13

O seguinte funciona no 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)
Gummbum
fonte
Execute que duas vezes e deixa de funcionar no Windows :-)
Michael Clerx
@MichaelClerx Mmm hmm, lembre-se sempre de fechar seus arquivos xD.
Python 3.5 no Raspbian 9 dá-me OSError: [Errno 29] Illegal seekpara a linhasys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs
12

Sim, está ativado por padrão. Você pode desativá-lo usando a opção -u na linha de comando ao chamar python.

Nathan
fonte
7

Você também pode executar o Python com o utilitário stdbuf :

stdbuf -oL python <script>

dyomas
fonte
2
O buffer de linha (como -oLhabilitado) ainda está em buffer - consulte f / e stackoverflow.com/questions/58416853/… , perguntando por end=''que a saída não é mais exibida imediatamente.
Charles Duffy
É verdade, mas o buffer de linha é o padrão (com um tty), então faz sentido escrever código, assumindo que a saída é totalmente sem buffer - talvez seja melhor explicitamente print(..., end='', flush=True)onde isso é improtante? OTOH, quando vários programas gravam na mesma saída simultaneamente, o trade-off tende a mudar de um progresso imediato para a redução de misturas de saída, e o buffer de linha se torna atraente. Então, talvez seja melhor não escrever explícito flushe controlar o buffer externamente?
Beni Cherniavsky-Paskin
Eu acho que não. O próprio processo deve decidir, quando e por que ele chama flush. O controle de buffer externo é uma solução alternativa
obrigatória
7

No Python 3, você pode aplicar um patch de macaco na função de impressão, para sempre enviar flush = True:

_orig_print = print

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

Conforme indicado em um comentário, você pode simplificar isso vinculando o parâmetro flush a um valor, via functools.partial:

print = functools.partial(print, flush=True)
Oliver
fonte
3
Apenas imaginando, mas esse não seria um caso de uso perfeito functools.partial?
0xC0000022L
Obrigado @ 0xC0000022L, isso faz com que pareça melhor! print = functools.partial(print, flush=True)funciona bem para mim.
MarSoft
@ 0xC0000022L de fato, eu atualizei o post para mostrar essa opção, obrigado por apontar isso
Oliver
3
Se você quiser que aplicar em todos os lugares,import builtins; builtins.print = partial(print, flush=True)
Perkins
4

Você também pode usar o fcntl para alterar os sinalizadores de arquivo rapidamente.

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)
jimx
fonte
1
Existe um equivalente do Windows: stackoverflow.com/questions/881696/…
Tobu
12
O_SYNC não tem nada a ver com o buffer no nível do espaço do usuário que esta pergunta está perguntando.
apenwarr
4

É possível substituir apenas o write método de sys.stdoutum que chama flush. A implementação do método sugerido está abaixo.

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

O valor padrão do wargumento manterá a writereferência do método original . Depois de write_flush definido, o original writepode ser substituído.

stdout.write = write_flush

O código assume que stdouté importado dessa maneira from sys import stdout.

Vasily E.
fonte
3

Você pode criar um arquivo sem buffer e atribuir esse arquivo a sys.stdout.

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

Você não pode alterar magicamente o stdout fornecido pelo sistema; desde que ele seja fornecido ao seu programa python pelo sistema operacional.

S.Lott
fonte
3

Variante que funciona sem travar (pelo menos no win32; python 2.7, ipython 0.12) e depois chamada posteriormente (várias vezes):

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)
Laimis
fonte
Tem certeza de que isso não está armazenado em buffer?
Quantum
1
Você deve procurar, em sys.stdout is sys.__stdout__vez de confiar no objeto de substituição que possui um atributo de nome?
leewz
isso funciona muito bem se o gunicorn não está respeitando PYTHONUNBUFFERED por algum motivo.
precisa
3

(Eu publiquei um comentário, mas ele se perdeu de alguma forma. Então, novamente :)

  1. Como notei, o CPython (pelo menos no Linux) se comporta de maneira diferente, dependendo de onde a saída vai. Se for para tty, a saída será liberada após cada ' \n'
    Se for para um tubo / processo, será armazenada em buffer e você poderá usar as flush()soluções baseadas ou a opção -u recomendada acima.

  2. Ligeiramente relacionado ao buffer de saída:
    se você iterar nas linhas na entrada com

    for line in sys.stdin:
    ...

então a implementação for no CPython coletará a entrada por um tempo e, em seguida, executará o corpo do loop para várias linhas de entrada. Se o seu script estiver prestes a gravar a saída para cada linha de entrada, isso pode parecer um buffer de saída, mas na verdade é um lote e, portanto, nenhuma das flush()técnicas etc. ajudará isso. Curiosamente, você não tem esse comportamento no pypy . Para evitar isso, você pode usar

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

tzp
fonte
Aqui está o seu comentário . Pode ser um bug nas versões mais antigas do Python. Você poderia fornecer código de exemplo? Algo como for line in sys.stdinvsfor line in iter(sys.stdin.readline, "")
jfs
para linha em sys.stdin: print ("Linha:" + linha); sys.stdout.flush ()
tzp
parece que o bug de leitura antecipada . Isso só deve acontecer no Python 2 e se stdin for um pipe. O código no meu comentário anterior demonstra o problema ( for line in sys.stdinfornece uma resposta atrasada)
jfs
2

Uma maneira de obter saída sem buffer seria usar em sys.stderrvez de sys.stdoutou simplesmente chamar sys.stdout.flush()para forçar explicitamente uma gravação a ocorrer.

Você pode redirecionar facilmente tudo impresso fazendo:

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

Ou para redirecionar apenas para uma printinstrução específica :

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

Para redefinir o stdout, você pode simplesmente fazer:

sys.stdout = sys.__stdout__
stderr
fonte
1
Isso pode ficar muito confuso quando você tenta capturar a saída mais tarde usando o redirecionamento padrão e descobre que não está capturando nada! ps seu stdout está sendo em negrito e outras coisas.
Freespace
1
Um grande cuidado ao imprimir seletivamente no stderr é que isso faz com que as linhas apareçam fora do lugar, portanto, a menos que você também tenha um carimbo de data / hora, isso pode ficar muito confuso.
haridsv