Tulis Python stdout ke file segera

51

Saat mencoba menulis stdout dari skrip Python ke file teks ( python script.py > log), file teks dibuat ketika perintah dimulai, tetapi konten yang sebenarnya tidak ditulis sampai skrip Python selesai. Sebagai contoh:

script.py:

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

mencetak ke stdout setiap 5 detik saat dipanggil dengan python script.py, tetapi ketika saya menelepon python script.py > log, ukuran file log tetap nol sampai skrip selesai. Apakah mungkin untuk langsung menulis ke file log, sehingga Anda dapat mengikuti kemajuan skrip (misalnya menggunakan tail)?

EDIT Ternyata yang python -u script.pymelakukan trik, saya tidak tahu tentang buffering stdout.

Bart
sumber
1
@ jezmck, saya bisa mengerti pertanyaan yang salah.
zyxue

Jawaban:

64

Hal ini terjadi karena biasanya ketika proses STDOUT dialihkan ke sesuatu selain terminal, maka output buffer ke beberapa buffer berukuran khusus OS (mungkin 4k atau 8k dalam banyak kasus). Sebaliknya, ketika mengeluarkan ke terminal, STDOUT akan menjadi buffer-line atau tidak buffer sama sekali, sehingga Anda akan melihat output setelah masing \n- masing atau untuk setiap karakter.

Anda biasanya dapat mengubah buffer STDOUT dengan stdbufutilitas:

stdbuf -oL python script.py > log

Sekarang jika Anda tail -F log, Anda akan melihat setiap output garis segera setelah dihasilkan.


Atau pembilasan eksplisit dari aliran output setelah setiap cetak harus mencapai hal yang sama. Sepertinya sys.stdout.flush()harus mencapai ini dengan Python. Jika Anda menggunakan Python 3.3 atau yang lebih baru, yang printfungsi juga memiliki flushkata kunci yang melakukan hal ini: print('hello', flush=True).

Trauma Digital
sumber
8
Terima kasih, saya tidak tahu tentang buffering! Mengetahui hal itu, Google dengan cepat memberi tahu saya bahwa python -u script.pyitu yang berhasil. EDIT Begitu banyak jawaban sekaligus, saya menerima jawaban Anda karena itu menunjuk saya ke arah penyangga.
Bart
1
@ julbra Keren, ya saya tidak tahu python juga memiliki opsi itu. Beberapa program command-line juga memiliki opsi serupa - misalnya --line-buffereduntuk grep, tetapi beberapa yang lain tidak. stdbufadalah utilitas catchall umum untuk menangani mereka yang tidak.
Digital Trauma
@ DigitalTrauma: Bukankah lebih baik tidak menggunakan buffering sama sekali yaitu stdbuf -o0 python script.py > logdalam keadaan yang ditentukan seperti ini?
heemayl
@heemayl -oLadalah kompromi. Secara umum buffer yang lebih besar akan memberikan kinerja yang lebih baik ketika mengarahkan ulang suatu tempat (lebih sedikit panggilan sistem dan lebih sedikit operasi I / O). Namun jika benar-benar diperlukan untuk melihat setiap karakter seperti yang dihasilkan maka ya, -o0akan diperlukan.
Digital Trauma
@ Paul Harap hindari menyalin konten yang menempel di antara jawaban, atau paling tidak sebutkan penulis asli yang menyediakan konten.
Bakuriu
44

Ini harus melakukan pekerjaan:

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

Karena Python akan buffer stdoutsecara default, di sini saya telah menggunakan sys.stdout.flush()flush buffer.

Solusi lain adalah dengan menggunakan -usaklar (unbuffered) dari python. Jadi, yang berikut juga akan berlaku:

python -u script.py >> log
heemayl
sumber
11

Variasi pada tema menggunakan opsi python sendiri untuk keluaran unbuffered akan digunakan #!/usr/bin/python -usebagai baris pertama.

Dengan #!/usr/bin/env pythonargumen tambahan itu tidak akan berfungsi, maka sebagai alternatif, seseorang dapat menjalankan PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txtatau melakukannya dalam dua langkah:

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

Anda harus beralih flush=Trueke printfungsi:

import time

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

Menurut dokumentasi, secara default, printtidak menerapkan apa pun tentang pembilasan:

Apakah output buffered biasanya ditentukan oleh file, tetapi jika flushargumen kata kunci itu benar, aliran secara paksa memerah.

Dan dokumentasi untuk sysstrems mengatakan:

Saat interaktif, stream standar diberi buffer-line. Kalau tidak, blok-buffered seperti file teks biasa. Anda dapat mengganti nilai ini dengan -uopsi baris perintah.


Jika Anda terjebak dengan versi python kuno, Anda harus memanggil flushmetode sys.stdoutstream:

import sys
import time

for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)
Bakuriu
sumber
1
Argumen flush = True bekerja dengan baik dengan Python 3.4.2, memang tidak bekerja dengan Python kuno (..) 2.7.9
Bart
Jawaban ini menyarankan hal yang sama dengan yang DigitalTraumadikatakan 10 jam sebelumnya. Anda harus memperbaiki posnya, bukan memposting hal yang sama lagi.
dotancohen
4
@dotancohen Sebenarnya bagian tentang print(flush=True)ditambahkan ke jawaban setelah saya oleh penulis pihak ketiga. Saya merasa tidak enak untuk merobek konten dari jawaban saya untuk meletakkannya di yang lain tanpa kredit. Saya memutuskan untuk menambahkan jawaban saya semata-mata karena tidak ada jawaban yang menyebutkan cara paling sederhana untuk mencapai apa yang diinginkan OP dalam versi python yang lebih baru, dan saya menambahkan "cara lama" hanya untuk kelengkapan. Lain kali silakan periksa riwayat revisi sebelum berkomentar dan atau downvoting.
Bakuriu
@ Bakuriu: Maaf kalau begitu! Ini menunjukkan alasan yang bagus untuk selalu memposting mengapa saat downvoting . Bisakah Anda mengedit posting sedikit sehingga saya dapat mengubah downvote saya menjadi upvote? Terima kasih!
dotancohen
Ini harus bekerja dengan Python 2,7 jika Anda melakukan __future__impor: from __future__ import print_function. Tapi ya, itu hanya untuk kompatibilitas dengan Python 3
Sergiy Kolodyazhnyy