Nonaktifkan buffering keluaran

532

Apakah buffering output diaktifkan secara default pada interpreter Python sys.stdout?

Jika jawabannya positif, apa saja cara untuk menonaktifkannya?

Saran sejauh ini:

  1. Gunakan -usaklar baris perintah
  2. Bungkus sys.stdoutsebuah objek yang memerah setelah setiap penulisan
  3. Setel PYTHONUNBUFFEREDenv var
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Apakah ada cara lain untuk mengatur beberapa flag global dalam sys/ secara sys.stdoutterprogram selama eksekusi?

Eli Bendersky
sumber
7
Untuk `print 'dalam Python 3, lihat jawaban ini .
Antti Haapala
1
Saya pikir kekurangannya -uadalah itu tidak akan berfungsi untuk bytecode yang dikompilasi atau untuk aplikasi dengan __main__.pyfile sebagai titik masuk.
akhan
Logika inisialisasi CPython lengkap ada di sini: github.com/python/cpython/blob/v3.8.2/Python/…
Beni Cherniavsky-Paskin

Jawaban:

443

Dari jawaban Magnus Lycka di milis :

Anda dapat melewati buffering untuk seluruh proses python menggunakan "python -u" (atau #! / Usr / bin / env python -u dll) atau dengan mengatur variabel lingkungan PYTHONUNBUFFERED.

Anda juga dapat mengganti sys.stdout dengan aliran lain seperti pembungkus yang melakukan flush setelah setiap panggilan.

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'
Seb
sumber
71
Sys.stdout asli masih tersedia sebagai sys .__ stdout__. Untuk berjaga-jaga jika Anda membutuhkannya =)
Antti Rasinen
40
#!/usr/bin/env python -utidak bekerja !! lihat di sini
wim
6
__getattr__hanya untuk menghindari warisan ?!
Vladimir Keleshev
32
Beberapa catatan untuk menghemat beberapa sakit kepala: Seperti yang saya perhatikan, buffering output berfungsi berbeda tergantung pada apakah output pergi ke tty atau proses / pipa lain. Jika pergi ke tty, maka itu memerah setelah masing-masing \ n , tetapi dalam pipa itu buffered. Dalam kasus terakhir, Anda dapat menggunakan solusi pembilasan ini. Dalam Cpython (bukan dalam pypy !!!): Jika Anda mengulangi input dengan untuk baris di sys.stdin: ... maka for loop akan mengumpulkan sejumlah baris sebelum badan loop dijalankan. Ini akan berperilaku seperti buffering, meskipun agak membingungkan. Sebaliknya, lakukan sementara true: line = sys.stdin.readline ()
tzp
5
@tzp: Anda bisa menggunakan iter()bukan whilelingkaran: for line in iter(pipe.readline, ''):. Anda tidak memerlukannya di Python 3 di mana for line in pipe:menghasilkan sesegera mungkin.
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)

Penghargaan: "Sebastian", di suatu tempat di milis Python.

Federico A. Ramponi
sumber
Dalam Python3 Anda bisa mengganti nama fungsi cetak dengan yang flushing. Ini trik kotor!
meawoppl
16
@meawoppl: Anda bisa meneruskan flush=Trueparameter agar print()berfungsi sejak Python 3.3.
jfs
Mengedit respons untuk menampilkan respons tidak valid dalam versi terbaru python
Mike
keduanya os.fdopen(sys.stdout.fileno(), 'wb', 0)(perhatikan buntuk biner) dan flush=Truebekerja untuk saya di 3.6.4. Namun, jika Anda menggunakan subproses untuk memulai skrip lain, pastikan Anda telah menentukan python3, jika Anda memiliki beberapa instance python yang diinstal.
not2qubit
1
@ not2qubit: jika Anda menggunakan os.fdopen(sys.stdout.fileno(), 'wb', 0)Anda berakhir dengan objek file biner, bukan TextIOaliran. Anda harus menambahkan a TextIOWrapperke dalam campuran (pastikan untuk memungkinkan write_throughuntuk menghilangkan semua buffer, atau gunakan line_buffering=Trueuntuk hanya menyiram pada baris baru).
Martijn Pieters
55

Ya itu.

Anda dapat menonaktifkannya di commandline dengan tombol "-u".

Atau, Anda dapat memanggil .flush () di sys.stdout pada setiap penulisan (atau membungkusnya dengan objek yang melakukan ini secara otomatis)

Brian
sumber
19

Ini berkaitan dengan jawaban Cristóvão D. Sousa, tetapi saya belum bisa berkomentar.

Cara mudah untuk menggunakan flushargumen kata kunci Python 3 agar selalu memiliki keluaran yang tidak dibangun adalah:

import functools
print = functools.partial(print, flush=True)

setelah itu, hasil cetak akan selalu menyiram output secara langsung (kecuali flush=Falsediberikan).

Perhatikan, (a) bahwa ini menjawab pertanyaan hanya sebagian karena tidak mengarahkan semua output. Tapi saya kira printadalah cara yang paling umum untuk membuat output ke stdout/ stderrdalam python, jadi 2 baris ini menutupi sebagian besar kasus penggunaan.

Perhatikan (b) bahwa itu hanya bekerja di modul / skrip tempat Anda mendefinisikannya. Ini bisa baik ketika menulis modul karena tidak mengacaukannya sys.stdout.

Python 2 tidak memberikan flushargumen, tetapi Anda bisa meniru printfungsi tipe-Python 3 seperti yang dijelaskan di sini https://stackoverflow.com/a/27991478/3734258 .

tim
sumber
1
Kecuali bahwa tidak ada flushkwarg di python2.
o11c
@ o11c, ya Anda benar. Saya yakin saya mengujinya tetapi entah bagaimana saya tampaknya bingung (: Saya mengubah jawaban saya, semoga baik-baik saja sekarang. Terima kasih!
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"])

Tanpa menyimpan sys.stdout lama, disable_stdout_buffering () tidak idempoten, dan beberapa panggilan akan menghasilkan kesalahan seperti ini:

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

Kemungkinan lain adalah:

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)

(Menambahkan ke gc.garbage bukanlah ide yang bagus karena itu adalah siklus yang tidak menyenangkan, dan Anda mungkin ingin memeriksanya.)

Mark Seaborn
sumber
2
Jika yang lama stdoutmasih hidup sys.__stdout__seperti yang disarankan beberapa orang, masalah sampah tidak perlu, kan? Ini trik yang keren.
Thomas Ahle
1
Seperti jawaban @ Federico, ini tidak akan bekerja dengan Python 3, karena akan membuang pengecualian ValueError: can't have unbuffered text I/Osaat memanggil print().
gbmhunter
"Kemungkinan lain" Anda tampaknya pada awalnya seperti solusi yang paling kuat, tetapi sayangnya itu menderita kondisi balapan dalam kasus bahwa utas lain memanggil terbuka () setelah sys.stdout.close () dan sebelum os.dup2 (temp_fd, fileno ). Saya menemukan ini ketika saya mencoba menggunakan teknik Anda di bawah ThreadSanitizer, yang melakukan hal itu. Kegagalan dibuat lebih keras oleh fakta bahwa dup2 () gagal dengan EBUSY ketika balapan dengan open () seperti itu; lihat stackoverflow.com/questions/23440216/…
Don Hatch
13

Karya-karya berikut di Python 2.6, 2.7, dan 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)
Gummbum
sumber
Jalankan itu dua kali dan crash pada windows :-)
Michael Clerx
@MichaelClerx Mmm hmm, selalu ingat untuk menutup file Anda xD.
Python 3.5 pada Raspbian 9 memberi saya OSError: [Errno 29] Illegal seekuntuk garissys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs
12

Ya, ini diaktifkan secara default. Anda dapat menonaktifkannya dengan menggunakan opsi -u pada baris perintah saat memanggil python.

Nathan
sumber
7

Anda juga dapat menjalankan Python dengan utilitas stdbuf :

stdbuf -oL python <script>

dyomas
sumber
2
Penyangga garis (seperti yang -oLdimungkinkan) masih penyangga - lihat f / e stackoverflow.com/questions/58416853/… , menanyakan mengapa end=''membuat keluaran tidak lagi segera ditampilkan.
Charles Duffy
Benar, tetapi line buffering adalah default (dengan tty), jadi apakah masuk akal untuk menulis kode dengan asumsi output sama sekali tidak terganggu - mungkin lebih baik untuk secara eksplisit di print(..., end='', flush=True)mana itu improtant? OTOH, ketika beberapa program menulis ke output yang sama secara bersamaan, trade-off cenderung bergeser dari melihat kemajuan langsung menjadi mengurangi campur-baur output, dan line buffering menjadi menarik. Jadi mungkin itu adalah lebih baik untuk tidak menulis eksplisit flushdan kontrol penyangga eksternal?
Beni Cherniavsky-Paskin
Saya pikir tidak. Proses itu sendiri harus memutuskan, kapan dan mengapa ia memanggil flush. Kontrol buffering eksternal harus
diselesaikan di
7

Dalam Python 3, Anda dapat menambal-kera fungsi cetak, untuk selalu mengirim flush = True:

_orig_print = print

def print(*args, **kwargs):
    _orig_print(*args, flush=True, **kwargs)

Seperti yang ditunjukkan dalam komentar, Anda dapat menyederhanakan ini dengan mengikat parameter flush ke sebuah nilai, melalui functools.partial:

print = functools.partial(print, flush=True)
Oliver
sumber
3
Hanya ingin tahu, tapi bukankah itu kasus penggunaan yang sempurna functools.partial?
0xC0000022L
Terima kasih @ 0xC0000022L, ini membuatnya terlihat lebih baik! print = functools.partial(print, flush=True)bekerja dengan baik untuk saya.
MarSoft
@ 0xC0000022L memang, saya telah memperbarui posting untuk menunjukkan pilihan itu, terima kasih telah menunjukkan itu
Oliver
3
Jika Anda ingin itu berlaku di mana-mana,import builtins; builtins.print = partial(print, flush=True)
Perkins
4

Anda juga dapat menggunakan fcntl untuk mengubah flag file secara langsung.

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)
Jimim
sumber
1
Ada yang setara dengan windows: stackoverflow.com/questions/881696/…
Tobu
12
O_SYNC tidak ada hubungannya sama sekali dengan buffer tingkat pengguna yang ditanyakan oleh pertanyaan ini.
apenwarr
4

Dimungkinkan untuk mengganti hanya write metode sys.stdoutdengan metode yang memanggil flush. Implementasi metode yang disarankan di bawah ini.

def write_flush(args, w=stdout.write):
    w(args)
    stdout.flush()

Nilai wargumen default akan menyimpan writereferensi metode asli . Setelah write_flush ditentukan, dokumen asli writemungkin diganti.

stdout.write = write_flush

Kode mengasumsikan yang stdoutdiimpor dengan cara ini from sys import stdout.

Dengan mudah E.
sumber
3

Anda dapat membuat file yang tidak dibangun dan menugaskan file ini ke sys.stdout.

import sys 
myFile= open( "a.log", "w", 0 ) 
sys.stdout= myFile

Anda tidak dapat secara ajaib mengubah stdout yang disediakan sistem; karena ini dipasok ke program python Anda oleh OS.

S.Lott
sumber
3

Varian yang berfungsi tanpa menabrak (setidaknya pada win32; python 2.7, ipython 0.12) kemudian dipanggil selanjutnya (beberapa kali):

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)
Laimis
sumber
Apakah Anda yakin ini tidak buffered?
kuantum
1
Haruskah Anda memeriksa sys.stdout is sys.__stdout__alih - alih mengandalkan objek pengganti yang memiliki atribut nama?
leewz
ini bekerja dengan baik jika gunicorn tidak menghormati PYTHONUNBUFFER karena suatu alasan.
Brian Arsuaga
3

(Saya sudah mengirim komentar, tapi entah bagaimana hilang. Jadi, lagi :)

  1. Seperti yang saya perhatikan, CPython (setidaknya di Linux) berperilaku berbeda tergantung dari mana outputnya. Jika pergi ke tty, maka output memerah setelah masing-masing ' \n'
    Jika pergi ke pipa / proses, maka buffered dan Anda dapat menggunakan flush()solusi berbasis atau opsi -u yang direkomendasikan di atas.

  2. Sedikit terkait dengan buffering keluaran:
    Jika Anda mengulangi garis di input dengan

    for line in sys.stdin:
    ...

maka untuk implementasi dalam CPython akan mengumpulkan input untuk sementara dan kemudian menjalankan loop body untuk sekelompok jalur input. Jika skrip Anda hendak menulis output untuk setiap baris input, ini mungkin terlihat seperti buffering output tetapi sebenarnya batching, dan karena itu, tidak ada satu pun flush(), dll teknik akan membantu itu. Menariknya, Anda tidak memiliki perilaku ini dalam pypy . Untuk menghindari ini, Anda bisa menggunakan

while True: line=sys.stdin.readline()
...

tzp
sumber
ini komentar anda . Mungkin ada bug pada versi Python yang lebih lama. Bisakah Anda memberikan kode contoh? Sesuatu seperti for line in sys.stdinvs.for line in iter(sys.stdin.readline, "")
jfs
untuk baris di sys.stdin: print ("Line:" + line); sys.stdout.flush ()
tzp
sepertinya bug baca-depan . Seharusnya hanya terjadi pada Python 2 dan jika stdin adalah pipa. Kode dalam komentar saya sebelumnya menunjukkan masalah ini ( for line in sys.stdinmemberikan respons yang tertunda)
jfs
2

Salah satu cara untuk mendapatkan keluaran yang tidak bermasalah adalah dengan menggunakan sys.stderralih-alih sys.stdoutatau hanya memanggil sys.stdout.flush()untuk secara eksplisit memaksa penulisan terjadi.

Anda dapat dengan mudah mengarahkan ulang semua yang dicetak dengan melakukan:

import sys; sys.stdout = sys.stderr
print "Hello World!"

Atau untuk mengarahkan ulang hanya untuk printpernyataan tertentu :

print >>sys.stderr, "Hello World!"

Untuk mengatur ulang stdout, Anda cukup melakukan:

sys.stdout = sys.__stdout__
stderr
sumber
1
Ini mungkin menjadi sangat membingungkan ketika Anda kemudian mencoba untuk menangkap output menggunakan pengalihan standar, dan menemukan Anda tidak menangkap apa pun! ps stdout Anda sedang dicetak tebal dan semacamnya.
freespace
1
Satu peringatan besar tentang pencetakan selektif ke stderr adalah bahwa hal ini menyebabkan garis-garis muncul tidak pada tempatnya, jadi kecuali jika Anda juga memiliki cap waktu, ini bisa sangat membingungkan.
haridsv