Çıktı tamponlamasını devre dışı bırak


532

Çıktı tamponlaması Python'un yorumlayıcısında varsayılan olarak etkin sys.stdoutmi?

Cevap olumluysa, devre dışı bırakmanın tüm yolları nelerdir?

Şimdiye kadarki öneriler:

  1. Kullanım -ukomut satırı anahtarı
  2. sys.stdoutHer yazma işleminden sonra temizlenen bir nesneye sarın
  3. PYTHONUNBUFFEREDEnv var ayarla
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Yürütme sırasında sys/ sys.stdoutprogramatik olarak genel bir bayrak belirlemenin başka bir yolu var mı ?


7
Python 3'te `` yazdır '' için bu cevaba bakınız .
Antti Haapala

1
Ben dezavantajı -uderlenmiş bayt kodu veya __main__.pygiriş noktası olarak bir dosyaya sahip uygulamalar için işe yaramaz olduğunu düşünüyorum .
akhan

Yanıtlar:


443

Gönderen bir posta listesinde Magnus Lycka cevap :

"Python -u" (veya #! / Usr / bin / env python -u vb.) Kullanarak veya PYTHONUNBUFFERED ortam değişkenini ayarlayarak tüm bir python işlemi için arabelleğe almayı atlayabilirsiniz.

Ayrıca sys.stdout'u, her çağrıdan sonra bir yıkama yapan sarıcı gibi başka bir akışla değiştirebilirsiniz.

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'

71
Orijinal sys.stdout hala sys .__ stdout__ olarak kullanılabilir. İhtiyacınız olursa =)
Antti Rasinen

40
#!/usr/bin/env python -uçalışmıyor !! buraya
wim

6
__getattr__sadece mirastan kaçınmak için mi ?!
Vladimir Keleshev

32
Bazı baş ağrılarını kaydetmek için bazı notlar: Fark ettiğim gibi, çıktı tamponlaması, çıktının bir tty veya başka bir işleme / boruya gitmesine bağlı olarak farklı çalışır. Bir tty'ye giderse, her bir \ n'den sonra yıkanır , ancak bir boruda arabelleğe alınır. İkinci durumda, bu yıkama çözümlerinden yararlanabilirsiniz. Cpython'da (pypy'de değil !!!): sys.stdin'deki satır için giriş üzerinden yinelerseniz: ... for döngüsü, döngü gövdesi çalıştırılmadan önce birkaç satır toplar. Bu, toplu iş gibi olsa da, tamponlama gibi davranacaktır. Bunun yerine, true iken yapın: line = sys.stdin.readline ()
tzp

5
@tzp: Kullanabileceğin iter()yerine whiledöngü: for line in iter(pipe.readline, ''):. Python 3'te for line in pipe:en kısa sürede verime ihtiyacınız yoktur .
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)

Kredi: "Sebastian", Python posta listesinde bir yerde.


Python3'te, yazdırma işlevinin adını bir yıkama işleviyle geçersiz kılabilirsiniz. Onun kirli bir hile olsa!
meawoppl

16
@meawoppl: Python 3.3'ten beri flush=Trueparametreyi print()işleve geçirebilirsiniz .
jfs

Yanıtı göstermek için yanıtı düzenlemek,
Mike

her ikisi de os.fdopen(sys.stdout.fileno(), 'wb', 0)( bikili için not ) ve flush=True3.6.4 benim için çalışır. Ancak, başka bir komut dosyası başlatmak için alt işlem kullanıyorsanız python3, birden fazla python örneği yüklediyseniz belirttiğinizden emin olun .
not2qubit

1
@ not2qubit: kullanırsanız os.fdopen(sys.stdout.fileno(), 'wb', 0)bir TextIOakış değil, bir ikili dosya nesnesi ile sonuçlanırsınız . TextIOWrapperKarışıma bir eklemeniz gerekir ( write_throughtüm arabellekleri kaldırmayı etkinleştirdiğinizden emin olun veya line_buffering=Trueyalnızca yeni satırlarda yıkamak için kullanın ).
Martijn Pieters

55

Evet öyle.

Komut satırında "-u" anahtarıyla devre dışı bırakabilirsiniz.

Alternatif olarak, her yazmada sys.stdout üzerinde .flush () yöntemini çağırabilirsiniz (veya bunu otomatik olarak yapan bir nesne ile sarabilirsiniz)


19

Bu Cristóvão D. Sousa'nın cevabı ile ilgilidir, ancak henüz yorum yapamadım.

flushAnahtar kelime argümanını kullanmanın basit bir yolu zaman arabelleksiz çıktıya sahip olmak için Python 3 :

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

daha sonra baskı daima çıktıyı doğrudan temizler ( flush=Falseverilenler hariç ).

(A) Sorunun tüm çıktıyı yeniden yönlendirmediği için yalnızca kısmen yanıt verdiğini unutmayın. Ama sanırımprint python'a stdout/ stderrpython çıktısı oluşturmanın en yaygın yolu , bu yüzden bu 2 satır muhtemelen kullanım durumlarının çoğunu kapsıyor.

(B) yalnızca tanımladığınız modül / komut dosyasında çalıştığını unutmayın. Bu, bir modülü yazarken iyi olmayabilir çünkü sys.stdout.

Python 2flush argümanı sağlamaz , ancak https://stackoverflow.com/a/27991478/3734258 adresindeprint açıklandığı gibi bir Python 3 tipi işlev taklit edebilirsiniz .


1
flushPython2'de kwarg olmaması dışında .
o11c

@ o11c, evet haklısın. Test ettiğimden emindim ama bir şekilde kafam karıştı (: Cevabımı değiştirdim, umarım şimdi iyi. Teşekkürler!
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"])

Eski sys.stdout kaydedilmeden, devre dışı_stdout_buffering () idempotent değildir ve birden fazla çağrı böyle bir hataya neden olur:

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

Başka bir olasılık:

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)

(Gc.garbage'a eklemek o kadar iyi bir fikir değildir çünkü çözülemez döngülerin konduğu yerdir ve bunları kontrol etmek isteyebilirsiniz.)


2
Eski stdouthala sys.__stdout__bazılarının önerdiği gibi yaşıyorsa , çöp şey gerekli olmayacak, değil mi? Yine de harika bir numara.
Thomas Ahle

1
@ Federico'un cevabında olduğu gibi, bu Python 3 ile çalışmaz, çünkü ValueError: can't have unbuffered text I/Oarama yaparken istisnayı atar print().
gbmhunter

"Başka bir olasılık" ilk başta en sağlam çözüm gibi görünüyor, ancak maalesef sys.stdout.close () öğesinden sonra ve os.dup2 (temp_fd, fileno) öğesinden önce başka bir iş parçacığının open () çağırması durumunda bir yarış durumu yaşıyor ). Ben tam olarak bunu yapar ThreadSanitizer altında tekniği kullanmaya çalıştığınızda bunu öğrendim. Başarısızlık, dup2 () 'nin open () ile yarıştığında EBUSY ile başarısız olması gerçeğiyle daha yüksek hale gelir; bkz. stackoverflow.com/questions/23440216/…
Don Hatch

13

Python 2.6, 2.7 ve 3.2'de aşağıdakiler çalışır:

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)

Bunu iki kez çalıştırın ve windows üzerinde çöküyor :-)
Michael Clerx

@MichaelClerx Mmm hmm, her zaman dosyalarınızı xD kapatmayı unutmayın.

Raspbian 9'daki Python 3.5 bana OSError: [Errno 29] Illegal seekhat veriyorsys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs

12

Evet, varsayılan olarak etkindir. Python'u çağırırken komut satırındaki -u seçeneğini kullanarak devre dışı bırakabilirsiniz.


7

Python'u stdbuf yardımcı programıyla da çalıştırabilirsiniz :

stdbuf -oL python <script>


2
Çizgi tamponlama (olarak -oLtanır) hala ara belleğe alınıyor - bkz f / e stackoverflow.com/questions/58416853/... , neden soran end=''çıktı artık hemen görüntülenecektir yapar.
Charles Duffy

Doğru, ancak satır arabelleğe alma (tty ile) varsayılan değerdir, bu nedenle çıktının tamamen arabelleğe alınmadığını varsayarak kod yazmak mantıklıdır - belki de bunun açık bir şekilde doğru print(..., end='', flush=True)olduğu yerde daha iyi ? OTOH, birkaç program aynı çıktıya eşzamanlı olarak yazdığında, değiş tokuş anında ilerleme görmekten çıktı karmalarını azaltmaya doğru kayma eğilimi gösterir ve satır tamponlaması cazip hale gelir. Öyleyse , açık bir şekilde yazmamak flushve tamponlamayı harici olarak kontrol etmemek daha iyidir ?
Beni Cherniavsky-Paskin

Bence hayır. Sürecin kendisi, ne zaman ve neden çağırdığına karar vermelidir flush. Harici tamponlama kontrolü burada geçici bir
çözümdür

7

Python 3'te, her zaman flush = True göndermek için yazdırma işlevini maymun yaması yapabilirsiniz:

_orig_print = print

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

Bir açıklamada belirtildiği gibi, flush parametresini şu şekilde bir değere bağlayarak basitleştirebilirsiniz functools.partial:

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

3
Sadece merak ediyorum, ama bunun için mükemmel bir kullanım durumu olmaz functools.partialmı?
0xC0000022L

Teşekkürler @ 0xC0000022L, bu daha iyi görünmesini sağlar! print = functools.partial(print, flush=True)benim için iyi çalışıyor.
MarSoft

@ 0xC0000022L Gerçekten, bu seçeneği göstermek için yazıyı güncelledim, işaret ettiğin için teşekkürler
Oliver

3
Her yere uygulamak istiyorsanız,import builtins; builtins.print = partial(print, flush=True)
Perkins

4

Dosya bayraklarını anında değiştirmek için fcntl komutunu da kullanabilirsiniz.

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)

1
Bir pencere eşdeğeri var: stackoverflow.com/questions/881696/…
Tobu

12
O_SYNC'nin, bu sorunun sorduğu kullanıcı alanı düzeyinde arabelleğe alma ile hiçbir ilgisi yoktur.
apenwarr

4

Geçersiz kılmak mümkündür tek write yöntemi sys.stdoutbir o çağrıları ile flush. Önerilen yöntem uygulaması aşağıdadır.

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

wArgümanın varsayılan değeri orijinal writeyöntem referansını koruyacaktır . Tanımlandıktan sonra write_flush orijinal writegeçersiz kılınabilir.

stdout.write = write_flush

Kod stdout, bu şekilde alındığını varsayar from sys import stdout.


3

Tamponsuz bir dosya oluşturabilir ve bu dosyayı sys.stdout'a atayabilirsiniz.

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

Sistem tarafından sağlanan stdout'u sihirli bir şekilde değiştiremezsiniz; çünkü işletim sistemi tarafından python programınıza verilir.


3

Çarpışmadan çalışan varyant (en azından win32; python 2.7, ipython 0.12) ve daha sonra çağrıldı (birden çok kez):

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)

Arabelleğe alınmadığından emin misiniz?
kuantum

1
sys.stdout is sys.__stdout__Bir isim özniteliğine sahip olan yedek nesneye güvenmek yerine kontrol etmeli misiniz ?
leewz

gunicorn bir sebepten dolayı PYTHONUNBUFFERED'e saygı duymuyorsa bu harika çalışır.
Brian Arsuaga

3

(Bir yorum gönderdim, ama bir şekilde kayboldu. Yani, tekrar :)

  1. Fark ettiğim gibi, CPython (en azından Linux'ta) çıktının nereye gittiğine bağlı olarak farklı davranıyor. Eğer bir tty'ye giderse, çıktı her birinden sonra temizlenir ' \n'
    Bir boruya / işleme giderse, arabelleğe alınır ve yukarıda önerilen flush()temel çözümleri veya -u seçeneğini kullanabilirsiniz .

  2. Çıktı tamponlamasıyla biraz ilgili:
    Girdi içindeki satırlar üzerinde

    for line in sys.stdin:
    ...

daha sonra CPython'daki for uygulaması , girişi bir süreliğine toplar ve daha sonra bir grup girdi satırı için döngü gövdesini yürütür. Betiğiniz her bir girdi satırı için çıktı yazmak üzereyse, bu çıktı arabelleğe alma gibi görünebilir, ancak aslında toplu iş yapar ve bu nedenle , vb. Tekniklerin hiçbiri buna yardımcı olmaz. İlginçtir, pypy'de bu davranışa sahip değilsiniz . Bundan kaçınmak için şunu kullanabilirsiniz:flush()

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


İşte yorumunuz . Eski Python sürümlerinde bir hata olabilir. Örnek kod verebilir misiniz? Gibi bir şey for line in sys.stdinvs.for line in iter(sys.stdin.readline, "")
jfs

sys.stdin'deki satır için: print ("Line:" + line); sys.stdout.flush ()
tzp

okuma hatası gibi görünüyor . Sadece Python 2'de ve stdin bir boru ise gerçekleşmelidir. Önceki for line in sys.stdin
yorumumdaki

2

Tamponsuz çıktı almanın bir yolu, çağrı yapmak sys.stderryerine kullanmak sys.stdoutveya sadecesys.stdout.flush() açıkça ortaya yazma zorlamak için.

Yazdırarak her şeyi kolayca yeniden yönlendirebilirsiniz:

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

Veya yalnızca belirli bir printifade için yönlendirmek için :

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

Stdout'u sıfırlamak için şunları yapabilirsiniz:

sys.stdout = sys.__stdout__

1
Daha sonra standart yönlendirme kullanarak çıktıyı yakalamaya çalıştığınızda ve hiçbir şey yakalamadığınızı fark ettiğinizde bu çok kafa karıştırıcı olabilir! ps stdout cesur ve şeyler.
freespace

1
Stderr'e seçici olarak yazdırma konusunda büyük bir uyarı, çizgilerin yerinden çıkmasına neden olmasıdır, bu nedenle zaman damgasına sahip olmadıkça bu çok kafa karıştırıcı olabilir.
haridsv
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.