Escreva o stdout do Python no arquivo imediatamente

51

Ao tentar gravar o stdout de um script Python em um arquivo de texto ( python script.py > log), o arquivo de texto é criado quando o comando é iniciado, mas o conteúdo real não é gravado até que o script Python seja concluído. Por exemplo:

script.py:

import time
for i in range(10):
    print('bla')
    time.sleep(5)

imprime no stdout a cada 5 segundos quando chamado python script.py, mas quando eu ligo python script.py > log, o tamanho do arquivo de log permanece zero até o script terminar. É possível gravar diretamente no arquivo de log, de forma que você possa acompanhar o progresso do script (por exemplo, usando tail)?

EDIT Acontece que python -u script.pyfaz o truque, eu não sabia sobre o buffer do stdout.

Bart
fonte
11
@ jezmck, eu poderia ter entendido a pergunta errada.
zyxue 12/06

Respostas:

64

Isso está acontecendo porque, normalmente, quando o processo STDOUT é redirecionado para algo diferente de um terminal, a saída é armazenada em buffer em algum buffer de tamanho específico do SO (talvez 4k ou 8k em muitos casos). Por outro lado, ao enviar para um terminal, o STDOUT terá buffer de linha ou nenhum buffer; portanto, você verá a saída após cada \nou para cada caractere.

Geralmente, você pode alterar o buffer STDOUT com o stdbufutilitário:

stdbuf -oL python script.py > log

Agora, se você tail -F log, você deve ver cada linha de saída imediatamente à medida que é gerada.


Como alternativa, a descarga explícita do fluxo de saída após cada impressão deve obter o mesmo. Parece que sys.stdout.flush()deve conseguir isso em Python. Se você estiver usando Python 3.3 ou mais recente, a printfunção também tem uma flushpalavra-chave que faz isso: print('hello', flush=True).

Trauma Digital
fonte
8
Obrigado, eu não sabia sobre o buffer! Sabendo disso, o Google rapidamente me disse que python -u script.pyfaz o truque. EDITAR Tantas respostas de uma só vez, eu aceitei a sua, uma vez que ela me apontava na direção do buffer.
Bart
11
@julbra Cool, sim, eu também não sabia que python tinha essa opção. Alguns programas de linha de comando também têm opções semelhantes - por exemplo , --line-bufferedpara grep, mas outros não. stdbufé o utilitário geral para lidar com aqueles que não o fazem.
Digital Trauma
@DigitalTrauma: Não é melhor não usar buffer, ou seja stdbuf -o0 python script.py > log, neste tipo de circunstâncias determinadas?
21320 heemayl
@heemayl -oLé um compromisso. Em geral, buffers maiores fornecerão melhor desempenho ao redirecionar para algum lugar (menos chamadas do sistema e menos operações de E / S). No entanto, se for absolutamente necessário ver cada caractere conforme é produzido, sim, -o0seria necessário.
Digital Trauma
@Paul Evite copiar e colar o conteúdo entre as respostas ou, pelo menos, mencione os autores originais que forneceram o conteúdo.
Bakuriu 03/02
44

Isso deve fazer o trabalho:

import time, sys
for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)

Como Python armazenará o buffer stdoutpor padrão, aqui eu usei sys.stdout.flush()para liberar o buffer.

Outra solução seria usar a opção -u(sem buffer) de python. Portanto, o seguinte também será feito:

python -u script.py >> log
heemail
fonte
11

A variação no tema de usar a própria opção do python para saída sem buffer seria usar #!/usr/bin/python -ucomo primeira linha.

Como #!/usr/bin/env pythonesse argumento extra não funcionará, então, como alternativa, é possível executá PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txt-lo ou executá -lo em duas etapas:

$ export PYTHONUNBUFFERED=1
$ ./myscript.py
Sergiy Kolodyazhnyy
fonte
10

Você deve passar flush=Truepara a printfunção:

import time

for i in range(10):
    print('bla', flush=True)
    time.sleep(5)

De acordo com a documentação, por padrão, printnão impõe nada sobre a liberação:

Se a saída é armazenada em buffer geralmente é determinada pelo arquivo, mas se o flushargumento da palavra - chave for verdadeiro, o fluxo será liberado à força.

E a documentação para sysstrems diz:

Quando interativos, os fluxos padrão são armazenados em buffer de linha. Caso contrário, eles são armazenados em buffer de bloco como arquivos de texto regulares. Você pode substituir esse valor pela -uopção de linha de comando.


Se você está preso a uma versão antiga do python, deve chamar o flushmétodo do sys.stdoutfluxo:

import sys
import time

for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)
Bakuriu
fonte
11
O argumento flush = True funciona muito bem com o Python 3.4.2, na verdade não funciona com o antigo (..) Python 2.7.9
Bart
Esta resposta sugere a mesma coisa que DigitalTraumadisse 10 horas antes. Você deve votar novamente na publicação dele, e não publicar a mesma coisa novamente.
dotancohen
4
@dotancohen Na verdade, a parte sobre print(flush=True)foi adicionada a essa resposta após a minha por um autor de terceiros. Acho que é de mau gosto extrair conteúdos da minha resposta para colocá-los em outro sem crédito. Decidi adicionar minha resposta apenas porque nenhuma resposta mencionava a maneira mais simples de alcançar o que o OP queria nas versões mais recentes do python, e adicionei a "maneira antiga" apenas por completude. Na próxima vez, verifique o histórico de revisões antes de comentar e / ou fazer voto negativo.
Bakuriu 03/02
@ Bakuriu: Sinto muito, então! Isso mostra um bom motivo para sempre postar o motivo da votação . Você poderia editar um pouco a postagem para que eu possa alterar meu voto negativo para um voto positivo? Obrigado!
dotancohen
Ele deve trabalhar com Python 2.7, se você fizer __future__importação: from __future__ import print_function. Mas sim, isso é apenas para compatibilidade com Python 3 #
Sergiy Kolodyazhnyy