Scrivi Python stdout sul file immediatamente


51

Quando si tenta di scrivere lo stdout da uno script Python in un file di testo ( python script.py > log), il file di testo viene creato all'avvio del comando, ma il contenuto effettivo non viene scritto fino al termine dello script Python. Per esempio:

script.py:

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

stampa su stdout ogni 5 secondi quando viene chiamato con python script.py, ma quando chiamo python script.py > log, la dimensione del file di registro rimane zero fino al termine dello script. È possibile scrivere direttamente nel file di registro, in modo da poter seguire l'avanzamento dello script (ad es. Utilizzando tail)?

EDIT Si scopre che python -u script.pyfa il trucco, non sapevo del buffering di stdout.


1
@jezmck, avrei potuto capire la domanda sbagliata.
zyxue,

Risposte:


64

Ciò accade perché normalmente quando il processo STDOUT viene reindirizzato a qualcosa di diverso da un terminale, l'output viene bufferizzato in un buffer di dimensioni specifiche del sistema operativo (forse 4k o 8k in molti casi). Al contrario, quando si effettua l'output su un terminale, STDOUT sarà bufferizzato in linea o non bufferizzato, quindi vedrai l'output dopo ciascuno \no per ciascun carattere.

In genere è possibile modificare il buffer STDOUT con l' stdbufutilità:

stdbuf -oL python script.py > log

Ora, se dovessi tail -F log, dovresti vedere immediatamente ogni linea in uscita mentre viene generata.


In alternativa, il lavaggio esplicito del flusso di output dopo ogni stampa dovrebbe ottenere lo stesso risultato. Sembra che sys.stdout.flush()dovrebbe raggiungere questo obiettivo in Python. Se si sta utilizzando Python 3.3 o più recente, la printfunzione ha anche una flushparola chiave che fa questo: print('hello', flush=True).


8
Grazie, non sapevo del buffering! Sapendo questo, Google mi ha detto abbastanza rapidamente che python -u script.pyfa il trucco. MODIFICA Tante risposte contemporaneamente, ho accettato le tue poiché mi ha indicato la direzione del buffering.
Bart,

1
@julbra Fantastico, sì, non sapevo che Python avesse quell'opzione. Alcuni programmi da riga di comando hanno anche opzioni simili, ad esempio --line-bufferedper grep, ma altri no. stdbufè l'utilità generale per gestire quelli che non lo fanno.
Trauma digitale

@DigitalTrauma: non è meglio non utilizzare alcun buffering, cioè stdbuf -o0 python script.py > login questo tipo di circostanze determinate?
heemayl

@heemayl -oLè un compromesso. In generale, buffer più grandi offrono prestazioni migliori quando si reindirizza da qualche parte (meno chiamate di sistema e meno operazioni di I / O). Tuttavia, se è assolutamente necessario vedere ogni carattere così come viene emesso, allora -o0sarebbe richiesto.
Trauma digitale

@Paul Si prega di evitare di incollare il contenuto tra le risposte, o almeno menzionare gli autori originali che hanno fornito il contenuto.
Bakuriu,

44

Questo dovrebbe fare il lavoro:

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

Poiché Python eseguirà il buffer stdoutper impostazione predefinita, qui ho utilizzato sys.stdout.flush()per svuotare il buffer.

Un'altra soluzione sarebbe quella di utilizzare l' -uopzione (senza buffer) di python. Quindi, farà anche questo:

python -u script.py >> log

11

La variazione sul tema dell'utilizzo dell'opzione di python per l'output senza buffer sarebbe quella di utilizzare #!/usr/bin/python -ucome prima riga.

Con #!/usr/bin/env pythonquell'argomento in più che non funzionerà, quindi in alternativa, si potrebbe eseguire PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txto farlo in due passaggi:

$ export PYTHONUNBUFFERED=1
$ ./myscript.py

10

Dovresti passare flush=Truealla printfunzione:

import time

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

Secondo la documentazione, per impostazione predefinita, printnon impone nulla sul lavaggio:

Se l'output è bufferizzato viene solitamente determinato dal file, ma se l' flushargomento della parola chiave è vero, il flusso viene forzatamente scaricato.

E la documentazione per sysgli strems dice:

Quando interattivi, i flussi standard sono bufferizzati. Altrimenti, sono bufferizzati a blocchi come normali file di testo. È possibile ignorare questo valore con l' -uopzione della riga di comando.


Se sei bloccato con una versione antica di Python devi chiamare il flushmetodo del sys.stdoutflusso:

import sys
import time

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

1
L'argomento flush = True funziona bene con Python 3.4.2, anzi non funziona con l'antico (..) Python 2.7.9
Bart

Questa risposta suggerisce la stessa cosa che DigitalTraumadiceva 10 ore prima. Dovresti votare il suo post, non pubblicare di nuovo la stessa cosa.
dotancohen,

4
@dotancohen In realtà la parte su è print(flush=True)stata aggiunta a quella risposta dopo la mia da un autore di terze parti. Trovo di cattivo gusto strappare i contenuti dalla mia risposta per metterli in un altro senza credito. Ho deciso di aggiungere la mia risposta solo perché nessuna risposta ha fornito alcuna menzione del modo più semplice per ottenere ciò che l'OP voleva nelle nuove versioni di Python e ho aggiunto il "vecchio modo" solo per completezza. La prossima volta si prega di controllare la cronologia delle revisioni prima di commentare o effettuare il downvoting.
Bakuriu,

@Bakuriu: mi dispiace allora! Questo dimostra un buon motivo per pubblicare sempre il motivo del downvoting . Potresti modificare un po 'il post in modo da poter cambiare il mio downvote in un voto? Grazie!
dotancohen,

Dovrebbe funzionare con Python 2.7 se si fa __future__di importazione: from __future__ import print_function. Ma sì, è solo per compatibilità con Python 3
Sergiy Kolodyazhnyy,
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.