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?


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'

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.


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)


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 .


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.)


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)

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.


7

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

stdbuf -oL python <script>


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)

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)

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.


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.


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)

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()
...


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__

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.