ปิดใช้งานการบัฟเฟอร์เอาต์พุต


532

การส่งออกบัฟเฟอร์เปิดใช้งานโดยค่าเริ่มต้นในตัวแปลของ Python ไว้sys.stdoutหรือไม่

หากคำตอบเป็นบวกทุกวิธีที่จะปิดการใช้งานคืออะไร?

คำแนะนำจนถึง:

  1. ใช้-uสวิตช์บรรทัดคำสั่ง
  2. ห่อsys.stdoutในวัตถุที่วูบวาบหลังจากการเขียนทุกครั้ง
  3. ตั้งค่าPYTHONUNBUFFEREDenv
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

มีวิธีอื่นในการตั้งค่าสถานะโกลบอลบางรายการในsys/ โดยทางsys.stdoutโปรแกรมในระหว่างการดำเนินการหรือไม่?


7
สำหรับ `print 'ใน Python 3 ดูคำตอบนี้
Antti Haapala

1
ฉันคิดว่าข้อเสียเปรียบของ-uมันคือมันจะไม่ทำงานสำหรับ bytecode ที่คอมไพล์หรือสำหรับแอพที่มี__main__.pyไฟล์เป็นจุดเริ่มต้น
akhan

ตรรกะการกำหนดค่าเริ่มต้น CPython เต็มอยู่ที่นี่: github.com/python/cpython/blob/v3.8.2/Python/…
Beni Cherniavsky-Paskin

คำตอบ:


443

จากคำตอบของ Magnus Lycka ในรายชื่อผู้รับจดหมาย :

คุณสามารถข้ามการบัฟเฟอร์สำหรับกระบวนการ python ทั้งหมดโดยใช้ "python -u" (หรือ #! / usr / bin / env python -u ฯลฯ ) หรือโดยการตั้งค่าตัวแปรสภาพแวดล้อม PYTHONUNBUFFERED

คุณยังสามารถแทนที่ sys.stdout ด้วยสตรีมอื่น ๆ เช่น wrapper ซึ่งจะล้างข้อมูลหลังจากการโทรทุกครั้ง

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
sys.stdout ดั้งเดิมยังคงเป็น sys .__ stdout__ ในกรณีที่คุณต้องการ =)
Antti Rasinen

40
#!/usr/bin/env python -uไม่ทำงาน !! ดูที่นี่
Wim

6
__getattr__เพียงเพื่อหลีกเลี่ยงการสืบทอด!
Vladimir Keleshev

32
บันทึกบางอย่างเพื่อบันทึกอาการปวดหัว: ดังที่ฉันสังเกตเห็นการบัฟเฟอร์เอาต์พุตทำงานแตกต่างกันไปขึ้นอยู่กับว่าถ้าเอาต์พุตไปที่ tty หรือกระบวนการ / ไพพ์อื่น ถ้ามันไปที่ tty มันจะถูกล้างหลังจาก\ nแต่ละตัวแต่ในไปป์นั้นจะถูกบัฟเฟอร์ ในกรณีหลังคุณสามารถใช้ประโยชน์จากวิธีการชำระล้างเหล่านี้ ใน Cpython (ไม่ใช่ใน pypy !!!): หากคุณวนซ้ำอินพุตกับสำหรับบรรทัดใน sys.stdin: ... ดังนั้นสำหรับลูปจะรวบรวมจำนวนบรรทัดก่อนที่จะเรียกใช้เนื้อความของลูป สิ่งนี้จะทำงานเหมือนการบัฟเฟอร์แม้ว่ามันจะค่อนข้างเป็นชุด ให้ทำในขณะที่เป็นจริง: line = sys.stdin.readline ()
tzp

5
@tzp: คุณสามารถใช้iter()แทนห่วง:while for line in iter(pipe.readline, ''):คุณไม่จำเป็นต้องใช้ Python 3 ที่for line in pipe:ให้ผลตอบแทนเร็วที่สุด
jfs

122

ฉันอยากจะใส่คำตอบของฉันในวิธีการล้างผลลัพธ์ของฟังก์ชั่นการพิมพ์? หรือในฟังก์ชั่นการพิมพ์ Python ที่ล้างบัฟเฟอร์เมื่อมันถูกเรียก? แต่เนื่องจากพวกเขาถูกทำเครื่องหมายว่าซ้ำซ้อนของอันนี้ (สิ่งที่ฉันไม่เห็นด้วย) ฉันจะตอบมันที่นี่

เนื่องจาก Python 3.3 พิมพ์ () สนับสนุนอาร์กิวเมนต์คำหลัก "flush" ( ดูเอกสารประกอบ ):

print('Hello World!', flush=True)

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)

เครดิต: "เซบาสเตียน" บางแห่งในรายชื่อผู้รับจดหมายของ Python


ใน Python3 คุณสามารถแทนที่ชื่อของฟังก์ชั่นการพิมพ์ด้วยการล้างข้อมูล มันเป็นเคล็ดลับสกปรก!
meawoppl

16
@meawoppl: คุณสามารถส่งflush=Trueพารามิเตอร์ให้print()ทำงานตั้งแต่ Python 3.3
jfs

การแก้ไขคำตอบเพื่อแสดงการตอบกลับไม่ถูกต้องใน python เวอร์ชันล่าสุด
Mike

ทั้งos.fdopen(sys.stdout.fileno(), 'wb', 0)(หมายเหตุbสำหรับไบนารี) และflush=Trueทำงานให้ฉันใน 3.6.4 อย่างไรก็ตามหากคุณใช้subprocessเพื่อเริ่มต้นสคริปต์อื่นตรวจสอบให้แน่ใจว่าคุณได้ระบุไว้python3หากคุณติดตั้งไพ ธ อนหลายอินสแตนซ์
not2qubit

1
@ not2qubit: หากคุณใช้os.fdopen(sys.stdout.fileno(), 'wb', 0)คุณท้ายด้วยวัตถุไฟล์ไบนารีไม่ใช่TextIOสตรีม คุณต้องเพิ่มTextIOWrapperการมิกซ์ (ต้องแน่ใจว่าได้เปิดใช้งานwrite_throughเพื่อกำจัดบัฟเฟอร์ทั้งหมดหรือใช้line_buffering=Trueเพื่อล้างเฉพาะการขึ้นบรรทัดใหม่)
Martijn Pieters

55

ใช่แล้ว.

คุณสามารถปิดการใช้งานมันบน commandline ด้วยสวิตช์ "-u"

อีกวิธีหนึ่งคือคุณสามารถเรียก. flush () บน sys.stdout ในทุก ๆ การเขียน (หรือล้อมด้วยวัตถุที่ทำสิ่งนี้โดยอัตโนมัติ)


19

สิ่งนี้เกี่ยวข้องกับคำตอบของCristóvão D. Sousa แต่ฉันยังไม่สามารถแสดงความคิดเห็นได้

วิธีที่ตรงไปข้างหน้าของการใช้flushอาร์กิวเมนต์คำหลักของงูหลาม 3เพื่อที่จะมักจะมีการส่งออก unbuffered คือ:

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

หลังจากนั้นการพิมพ์จะล้างข้อมูลโดยตรงเสมอ (ยกเว้นflush=Falseจะได้รับ)

โปรดทราบว่า (a) คำถามนี้ตอบคำถามเพียงบางส่วนเท่านั้นเนื่องจากไม่ได้เปลี่ยนทิศทางเอาต์พุตทั้งหมด แต่ฉันเดาว่าprintเป็นวิธีที่ใช้กันมากที่สุดในการสร้างเอาต์พุตไปยังstdout/ stderrในไพ ธ อนดังนั้น 2 บรรทัดนี้จึงครอบคลุมกรณีการใช้งานส่วนใหญ่

หมายเหตุ (b) ใช้งานได้เฉพาะในโมดูล / สคริปต์ที่คุณกำหนดไว้เท่านั้น sys.stdoutนี้สามารถที่ดีเมื่อเขียนโมดูลเป็นก็ไม่ได้ยุ่งกับ

งูหลาม 2ไม่ได้ให้flushการโต้แย้ง แต่คุณสามารถเลียนแบบงูหลาม 3 ประเภทprintฟังก์ชั่นตามที่อธิบายไว้ที่นี่https://stackoverflow.com/a/27991478/3734258


1
ยกเว้นว่าไม่มีflushkwarg ใน python2
o11c

@ o11c ใช่คุณพูดถูก ฉันแน่ใจว่าฉันทดสอบ แต่อย่างใดฉันก็สับสนดูเหมือน (ฉันปรับเปลี่ยนคำตอบของฉันหวังว่ามันดีตอนนี้ขอบคุณ!
ทิม

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"])

หากไม่บันทึก sys.stdout เก่า ๆ disable_stdout_buffering () ไม่ใช่ idempotent และการโทรหลายครั้งจะทำให้เกิดข้อผิดพลาดดังนี้:

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

ความเป็นไปได้อีกอย่างคือ:

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 นั้นไม่ใช่ความคิดที่ดีเพราะเป็นที่ที่วงจรไม่เป็นระเบียบและคุณอาจต้องการตรวจสอบสิ่งเหล่านั้น)


2
หากคนชราstdoutยังมีชีวิตอยู่sys.__stdout__ตามที่บางคนแนะนำสิ่งขยะไม่จำเป็นใช่ไหม มันเป็นเคล็ดลับที่ยอดเยี่ยม
โทมัส Ahle

1
เช่นเดียวกับคำตอบ @ Federico ของนี้จะไม่ทำงานกับงูหลาม 3 ขณะที่มันจะโยนข้อยกเว้นเมื่อโทรValueError: can't have unbuffered text I/O print()
gbmhunter

ดูเหมือนว่า "ความเป็นไปได้อีกอย่าง" ของคุณในตอนแรกจะเป็นโซลูชันที่มีประสิทธิภาพที่สุด แต่น่าเสียดายที่สภาพการแข่งขันในกรณีที่เธรดอื่นเรียกใช้เปิด () หลังจาก sys.stdout.close () ของคุณและก่อนหน้า os.dup2 (temp_fd, fileno) ) ฉันพบสิ่งนี้เมื่อฉันลองใช้เทคนิคของคุณภายใต้ ThreadSanitizer ซึ่งทำสิ่งนั้นอย่างแน่นอน ความล้มเหลวนั้นเกิดจากความจริงที่ว่า dup2 () ล้มเหลวด้วย EBUSY เมื่อมันแข่งกับ open () แบบนั้น ดูstackoverflow.com/questions/23440216/…
Don Hatch

13

การทำงานต่อไปนี้ใน Python 2.6, 2.7 และ 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)

เรียกใช้สองครั้งและมันล้มเหลวบน windows :-)
Michael Clerx

@MichaelClerx Mmm hmm อย่าลืมปิดไฟล์ของคุณเสมอ

งูหลาม 3.5 ใน Raspbian 9 ทำให้ผมOSError: [Errno 29] Illegal seekเส้นsys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs

12

ใช่มันถูกเปิดใช้งานโดยค่าเริ่มต้น คุณสามารถปิดการใช้งานได้โดยใช้ตัวเลือก -u บนบรรทัดคำสั่งเมื่อเรียก python


7

คุณสามารถรัน Python ด้วยยูทิลิตี้stdbuf :

stdbuf -oL python <script>


2
การบัฟเฟอร์บรรทัด (ตามที่-oLเปิดใช้งาน) ยังคงเป็นการบัฟเฟอร์ - ดู f / e stackoverflow.com/questions/58416853/ ......ถามว่าเหตุใดจึงend=''ไม่แสดงผลลัพธ์ในทันที
ชาร์ลส์ดัฟฟี่

เป็นจริง แต่การบัฟเฟอร์บรรทัดเป็นค่าเริ่มต้น (ด้วย tty) ดังนั้นการเขียนโค้ดที่สมมติว่าเอาต์พุตนั้นไม่มีค่าคงที่โดยสิ้นเชิง - อาจดีกว่าถ้าคุณระบุprint(..., end='', flush=True)ตำแหน่งที่ improtant? OTOH เมื่อหลาย ๆ โปรแกรมเขียนไปยังเอาต์พุตเดียวกันพร้อมกันการแลกเปลี่ยนมีแนวโน้มที่จะเปลี่ยนจากการเห็นความคืบหน้าในทันทีเพื่อลดการมิกซ์เอาท์พุทและการบัฟเฟอร์บรรทัดกลายเป็นที่ดึงดูด ดังนั้นบางทีมันอาจจะเป็นดีกว่าที่จะไม่เขียนอย่างชัดเจนflushและการควบคุมบัฟเฟอร์ภายนอก?
Beni Cherniavsky-Paskin

ฉันคิดว่าไม่ flushประมวลผลตัวเองควรจะตัดสินใจเมื่อใดและทำไมมันเรียกร้อง การควบคุมการบัฟเฟอร์ภายนอกถูกบังคับให้ต้องแก้ไขที่นี่
dyomas

7

ใน Python 3 คุณสามารถ monkey-patch ฟังก์ชั่นการพิมพ์เพื่อส่ง flush = True:

_orig_print = print

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

ตามที่ระบุไว้ในความคิดเห็นคุณสามารถทำให้สิ่งนี้ง่ายขึ้นโดยการผูกพารามิเตอร์ flush กับค่าผ่านfunctools.partial:

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

3
แค่สงสัย แต่นั่นจะไม่เหมาะกับการใช้งานfunctools.partialใช่หรือไม่
0xC0000022L

ขอบคุณ @ 0xC0000022L มันทำให้ดูดีขึ้น! print = functools.partial(print, flush=True)ทำงานได้ดีสำหรับฉัน
MarSoft

@ 0xC0000022L แน่นอนฉันมีการปรับปรุงการโพสต์ที่จะแสดงตัวเลือกว่าขอบคุณสำหรับการชี้ว่า
โอลิเวอร์

3
ถ้าคุณต้องการที่จะนำไปใช้ทุกที่import builtins; builtins.print = partial(print, flush=True)
Perkins

4

คุณยังสามารถใช้ fcntl เพื่อเปลี่ยนแฟล็กไฟล์ในทันที

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
มีหน้าต่างที่เทียบเท่า: stackoverflow.com/questions/881696/…
Tobu

12
O_SYNC ไม่มีส่วนเกี่ยวข้องกับการกำหนดบัฟเฟอร์ระดับพื้นที่ผู้ใช้ที่คำถามนี้ถาม
apenwarr

4

มันเป็นไปได้ที่จะแทนที่เพียง writeวิธีการกับคนที่โทรsys.stdout flushวิธีการใช้งานที่แนะนำอยู่ด้านล่าง

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

ค่าเริ่มต้นของการwโต้แย้งจะเก็บwriteวิธีการอ้างอิงเดิม หลังจาก write_flushมีการกำหนดต้นฉบับwriteอาจถูกเขียนทับ

stdout.write = write_flush

รหัสจะถือว่าstdoutมีการนำเข้าด้วยวิธีfrom sys import stdoutนี้


3

คุณสามารถสร้างไฟล์ที่ไม่มีบัฟเฟอร์และกำหนดไฟล์นี้ให้กับ sys.stdout

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

คุณไม่สามารถเปลี่ยน stdout ที่ระบบจัดหาได้อย่างน่าอัศจรรย์ เนื่องจากมันถูกส่งไปยังโปรแกรม python ของคุณโดยระบบปฏิบัติการ


3

ตัวแปรที่ทำงานโดยไม่หยุดทำงาน (อย่างน้อยใน win32; python 2.7, ipython 0.12) จากนั้นเรียกใช้ในภายหลัง (หลายครั้ง):

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)

คุณแน่ใจหรือว่านี่ไม่ได้ถูกบัฟเฟอร์?
ควอนตัม

1
คุณควรตรวจสอบsys.stdout is sys.__stdout__แทนที่จะใช้การแทนที่วัตถุที่มีแอตทริบิวต์ชื่อหรือไม่
leewz

วิธีนี้ใช้งานได้ดีถ้า gunicorn ไม่เคารพ PYTHONUNBUFFERED ด้วยเหตุผลบางอย่าง
Brian Arsuaga

3

(ฉันได้โพสต์ความคิดเห็น แต่มันก็หายไปอย่างนั้นอีกครั้ง :)

  1. ตามที่ฉันสังเกตเห็น CPython (อย่างน้อยบน Linux) จะทำงานแตกต่างกันไปขึ้นอยู่กับตำแหน่งที่ส่งออก หากไปที่ tty เอาต์พุตจะถูกล้างออกหลังจากแต่ละ ' \n'
    หากไปยังไพพ์ / กระบวนการก็จะถูกบัฟเฟอร์และคุณสามารถใช้flush()โซลูชันพื้นฐานหรือตัวเลือก-u ที่แนะนำข้างต้น

  2. เกี่ยวข้องเล็กน้อยกับการบัฟเฟอร์เอาต์พุต:
    หากคุณวนซ้ำบรรทัดในอินพุตด้วย

    for line in sys.stdin:
    ...

ดังนั้นสำหรับการนำไปใช้ในCPythonจะรวบรวมอินพุตชั่วขณะหนึ่งจากนั้นเรียกใช้เนื้อความลูปสำหรับกลุ่มของอินพุตบรรทัด หากสคริปต์ของคุณกำลังจะเขียนเอาต์พุตสำหรับแต่ละบรรทัดอินพุตสิ่งนี้อาจดูเหมือนบัฟเฟอร์เอาต์พุต แต่จริงๆแล้วเป็นการแบ็ตช์และดังนั้นจึงไม่มีflush()เทคนิคและอื่น ๆ ที่จะช่วยได้ ที่น่าสนใจที่คุณไม่ได้มีพฤติกรรมในpypy เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถใช้

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


นี่คือความคิดเห็นของคุณ อาจเป็นข้อผิดพลาดของ Python เวอร์ชั่นเก่า คุณสามารถให้รหัสตัวอย่างได้หรือไม่ สิ่งที่คล้ายfor line in sys.stdinกับfor line in iter(sys.stdin.readline, "")
jfs

สำหรับบรรทัดใน sys.stdin: print ("Line:" + line); sys.stdout.flush ()
tzp

ดูเหมือนว่าข้อผิดพลาดการอ่านล่วงหน้า มันควรจะเกิดขึ้นกับ Python 2 เท่านั้นและถ้า stdin เป็นไพพ์ รหัสในความคิดเห็นก่อนหน้าของฉันแสดงให้เห็นถึงปัญหา ( for line in sys.stdinให้การตอบสนองล่าช้า)
jfs

2

วิธีหนึ่งในการรับเอาต์พุตที่ไม่มีบัฟเฟอร์จะใช้sys.stderrแทนsys.stdoutหรือเพียงแค่เรียกsys.stdout.flush()เพื่อบังคับให้เขียนอย่างชัดเจน

คุณสามารถเปลี่ยนเส้นทางทุกอย่างที่พิมพ์โดยทำอย่างง่ายดาย:

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

หรือเปลี่ยนเส้นทางสำหรับข้อความเฉพาะprint:

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

หากต้องการรีเซ็ต stdout คุณสามารถทำได้:

sys.stdout = sys.__stdout__

1
สิ่งนี้อาจทำให้เกิดความสับสนมากเมื่อคุณพยายามจับภาพเอาต์พุตในภายหลังโดยใช้การเปลี่ยนเส้นทางมาตรฐานและพบว่าคุณไม่ได้จับอะไรเลย! ps stdoutของคุณกำลังเป็นตัวหนาและสิ่งต่าง ๆ
FreeSpace

1
ข้อควรระวังอย่างหนึ่งที่สำคัญเกี่ยวกับการเลือกการพิมพ์ไปยัง stderr คือสิ่งนี้ทำให้เส้นปรากฏขึ้นนอกสถานที่ดังนั้นหากคุณยังมีการประทับเวลาสิ่งนี้อาจทำให้เกิดความสับสนได้
haridsv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.