Escreva a saída do Python para o arquivo imediatamente

38

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 para stdout a cada 5 segundos quando chamado com python script.py , mas quando eu chamo python script.py > log , o tamanho do arquivo de log permanece zero até que o script termine. É possível gravar diretamente no arquivo de log, de modo que você possa acompanhar o andamento do script (por exemplo, usando tail )?

EDIT Acontece que python -u script.py faz o truque, eu não sabia sobre o buffer de stdout.

    
por Bart 02.02.2015 / 22:41

4 respostas

52

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 será armazenado em buffer de linha ou não será armazenado em buffer, portanto, você verá a saída após cada \n ou para cada caractere.

Você geralmente pode alterar o armazenamento em buffer do STDOUT com o utilitário stdbuf :

stdbuf -oL python script.py > log

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

Alternativamente, o fluxo explícito do fluxo de saída após cada impressão deve alcançar o mesmo. Parece que o sys.stdout.flush() deve conseguir isso em Python. Se você estiver usando o Python 3.3 ou mais recente, a função print também terá uma palavra-chave flush que faz isso: print('hello', flush=True) .

    
por 02.02.2015 / 22:54
32

Isso deve fazer o trabalho:

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

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

Outra solução seria usar a opção -u (unbuffered) de python . Então, o seguinte também fará:

python -u script.py >> log
    
por 02.02.2015 / 22:55
7

Você deve passar flush=True para a função print :

import time

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

De acordo com a documentação, por padrão, print não impõe nada sobre o flushing:

Whether output is buffered is usually determined by file, but if the flush keyword argument is true, the stream is forcibly flushed.

E a documentação de sys diz:

When interactive, standard streams are line-buffered. Otherwise, they are block-buffered like regular text files. You can override this value with the -u command-line option.

Se você está preso a uma versão antiga do python, é necessário chamar o flush do sys.stdout stream:

import sys
import time

for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)
    
por 03.02.2015 / 09:19
6

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

Com #!/usr/bin/env python esse argumento extra não funciona, então, alternativamente, pode-se executar PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txt ou fazer isso em duas etapas:

$ export PYTHONUNBUFFERED=1
$ ./myscript.py
    
por 26.10.2017 / 09:57