Vô hiệu hóa bộ đệm đầu ra


532

Bộ đệm đầu ra có được bật theo mặc định trong trình thông dịch của Python sys.stdoutkhông?

Nếu câu trả lời là tích cực, tất cả các cách để vô hiệu hóa nó là gì?

Gợi ý cho đến nay:

  1. Sử dụng -uchuyển đổi dòng lệnh
  2. Bọc sys.stdouttrong một đối tượng tuôn ra sau mỗi lần viết
  3. Đặt PYTHONUNBUFFEREDenv var
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Có cách nào khác để đặt một số cờ toàn cầu trong sys/ sys.stdoutlập trình trong khi thực hiện không?


7
Đối với 'in' trong Python 3, xem câu trả lời này .
Antti Haapala

1
Tôi nghĩ một nhược điểm của -unó là nó sẽ không hoạt động đối với mã byte được biên dịch hoặc cho các ứng dụng có __main__.pytệp là điểm nhập cảnh.
akhan

Logic khởi tạo CPython đầy đủ có ở đây: github.com/python/cpython/blob/v3.8.2/Python/ Kẻ
Beni Cherniavsky-Paskin

Câu trả lời:


443

Từ câu trả lời của Magnus Lycka trong danh sách gửi thư :

Bạn có thể bỏ qua việc đệm cho toàn bộ quá trình python bằng cách sử dụng "python -u" (hoặc #! / Usr / bin / env python -u, v.v.) hoặc bằng cách đặt biến môi trường PYTHONUNBUFFERED.

Bạn cũng có thể thay thế sys.stdout bằng một số luồng khác như trình bao bọc sẽ thực hiện sau mỗi cuộc gọi.

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 gốc vẫn có sẵn dưới dạng sys .__ stdout__. Chỉ trong trường hợp bạn cần nó =)
Antti Rasinen

40
#!/usr/bin/env python -ukhông hoạt động !! xem tại đây
wim

6
__getattr__chỉ để tránh thừa kế?!
Vladimir Keleshev

32
Một số lưu ý để tiết kiệm một số vấn đề đau đầu: Như tôi nhận thấy, bộ đệm đầu ra hoạt động khác nhau tùy thuộc vào việc đầu ra đi đến một tty hay một quy trình / ống khác. Nếu nó đi đến một tty, thì nó sẽ bị xóa sau mỗi \ n , nhưng trong một đường ống, nó được đệm. Trong trường hợp sau, bạn có thể sử dụng các giải pháp xả nước này. Trong Cpython (không phải trong pypy !!!): Nếu bạn lặp lại đầu vào với dòng for trong sys.stdin: ... thì vòng lặp for sẽ thu thập một số dòng trước khi phần thân của vòng lặp được chạy. Điều này sẽ hoạt động giống như bộ đệm, mặc dù nó khá theo đợt. Thay vào đó, hãy làm trong khi true: line = sys.stdin.readline ()
tzp

5
@tzp: bạn có thể sử dụng iter()thay vì whilevòng lặp : for line in iter(pipe.readline, ''):. Bạn không cần nó trên Python 3, nơi for line in pipe:mang lại càng sớm càng tốt.
jfs

122

Tôi muốn đặt câu trả lời của tôi trong Làm thế nào để tuôn ra đầu ra của chức năng in? hoặc trong chức năng in của Python để xóa bộ đệm khi nó được gọi? , nhưng vì chúng được đánh dấu là bản sao của cái này (những gì tôi không đồng ý), tôi sẽ trả lời nó ở đây.

Vì Python 3.3, print () hỗ trợ đối số từ khóa "flush" ( xem tài liệu ):

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)

Tín dụng: "Sebastian", ở đâu đó trong danh sách gửi thư của Python.


Trong Python3, bạn chỉ có thể ghi đè tên của chức năng in bằng cách xóa. Đó là một mánh khóe bẩn thỉu!
meawoppl

16
@meawoppl: bạn có thể truyền flush=Truetham số cho print()hàm kể từ Python 3.3.
jfs

Chỉnh sửa phản hồi để hiển thị phản hồi không hợp lệ trong phiên bản gần đây của python
Mike

cả hai os.fdopen(sys.stdout.fileno(), 'wb', 0)(lưu ý bcho nhị phân) và flush=Truelàm việc cho tôi trong 3.6.4. Tuy nhiên, nếu bạn đang sử dụng quy trình con để bắt đầu một tập lệnh khác, hãy đảm bảo bạn đã chỉ định python3, nếu bạn đã cài đặt nhiều phiên bản python.
not2qubit

1
@ not2qubit: nếu bạn sử dụng, os.fdopen(sys.stdout.fileno(), 'wb', 0)bạn kết thúc với một đối tượng tệp nhị phân, không phải là một TextIOluồng. Bạn sẽ phải thêm một TextIOWrapperhỗn hợp (đảm bảo bật write_throughđể loại bỏ tất cả các bộ đệm hoặc sử dụng line_buffering=Trueđể chỉ xóa trên dòng mới).
Martijn Pieters

55

Vâng, đúng vậy.

Bạn có thể vô hiệu hóa nó trên dòng lệnh bằng công tắc "-u".

Ngoài ra, bạn có thể gọi .flush () trên sys.stdout trên mỗi lần ghi (hoặc bọc nó bằng một đối tượng thực hiện điều này tự động)


19

Điều này liên quan đến câu trả lời của Cristóvão D. Sousa, nhưng tôi chưa thể bình luận.

Một cách đơn giản để sử dụng flushđối số từ khóa của Python 3 để luôn có đầu ra không có bộ đệm là:

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

sau đó, in sẽ luôn xả trực tiếp đầu ra (ngoại trừ flush=Falseđược đưa ra).

Lưu ý, (a) rằng điều này chỉ trả lời một phần vì nó không chuyển hướng tất cả đầu ra. Nhưng tôi đoán printlà cách phổ biến nhất để tạo đầu ra stdout/ stderrtrong python, vì vậy 2 dòng này có thể bao gồm hầu hết các trường hợp sử dụng.

Lưu ý (b) rằng nó chỉ hoạt động trong mô-đun / tập lệnh mà bạn đã xác định nó. Điều này có thể tốt khi viết một mô-đun vì nó không gây rối với sys.stdout.

Python 2 không cung cấp flushđối số, nhưng bạn có thể mô phỏng hàm 3 loại Python printnhư được mô tả ở đây https://stackoverflow.com/a/27991478/3734258 .


1
Ngoại trừ việc không có flushkwarg trong python2.
o11c

@ o11c, vâng, bạn đúng. Tôi chắc chắn tôi đã kiểm tra nó nhưng bằng cách nào đó tôi dường như bối rối (: Tôi đã sửa đổi câu trả lời của mình, hy vọng nó ổn ngay bây giờ. Cảm ơn!
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"])

Nếu không lưu sys.stdout cũ, vô hiệu hóa_stdout_buffering () không phải là idempotent và nhiều cuộc gọi sẽ dẫn đến một lỗi như thế này:

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

Một khả năng khác là:

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)

(Đăng ký vào gc.garbage không phải là một ý tưởng hay vì đó là nơi đặt các chu kỳ không thể thực hiện được và bạn có thể muốn kiểm tra chúng.)


2
Nếu người cũ stdoutvẫn sống sys.__stdout__như một số người đã đề xuất, thì thứ rác rưởi sẽ không cần thiết, phải không? Đó là một mẹo hay mặc dù.
Thomas Ahle

1
Như với câu trả lời của @ Federico, điều này sẽ không hoạt động với Python 3, vì nó sẽ ném ngoại lệ ValueError: can't have unbuffered text I/Okhi gọi print().
gbmhunter

"Khả năng khác" của bạn thoạt đầu giống như giải pháp mạnh mẽ nhất, nhưng thật không may, nó gặp phải tình trạng chủng tộc trong trường hợp một luồng khác gọi open () sau sys.stdout.close () và trước os.dup2 (temp_fd, fileno của bạn ). Tôi đã tìm thấy điều này khi tôi thử sử dụng kỹ thuật của bạn trong ThreadSanitizer, chính xác là như vậy. Thất bại được làm cho to hơn bởi thực tế là dup2 () thất bại với EBUSY khi nó chạy với open () như thế; xem stackoverflow.com/questions/23440216/ Mạnh
Don nở

13

Các công việc sau đây trong Python 2.6, 2.7 và 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)

Chạy hai lần và nó gặp sự cố trên windows :-)
Michael Clerx

@MichaelClerx Mmm hmm, luôn nhớ đóng các tệp xD của bạn.

Python 3.5 trên Raspbian 9 cung cấp cho tôi OSError: [Errno 29] Illegal seekdòngsys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs

12

Vâng, nó được kích hoạt theo mặc định. Bạn có thể vô hiệu hóa nó bằng cách sử dụng tùy chọn -u trên dòng lệnh khi gọi python.


7

Bạn cũng có thể chạy Python với tiện ích stdbuf :

stdbuf -oL python <script>


2
Bộ đệm dòng (như -oLcho phép) vẫn là bộ đệm - xem f / e stackoverflow.com/questions/58416853/ , hỏi tại sao end=''làm cho đầu ra không còn được hiển thị ngay lập tức.
Charles Duffy

Đúng, nhưng bộ đệm dòng là mặc định (với một tty) vì vậy có ý nghĩa gì khi viết mã giả sử đầu ra hoàn toàn không có bộ đệm - có thể tốt hơn để rõ ràng print(..., end='', flush=True)ở nơi không phù hợp? OTOH, khi một số chương trình ghi vào cùng một đầu ra đồng thời, sự đánh đổi có xu hướng chuyển từ nhìn thấy tiến bộ ngay lập tức sang giảm trộn lẫn đầu ra và bộ đệm dòng trở nên hấp dẫn. Vì vậy, có lẽ tốt hơn không viết rõ ràng flushvà kiểm soát bộ đệm bên ngoài?
Beni Cherniavsky-Paskin

Tôi nghĩ rằng không. Quá trình tự quyết định, khi nào và tại sao nó gọi flush. Kiểm soát bộ đệm bên ngoài là cách giải quyết bắt buộc ở đây
dyomas

7

Trong Python 3, bạn có thể vá lỗi chức năng in, để luôn gửi flush = True:

_orig_print = print

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

Như đã chỉ ra trong một nhận xét, bạn có thể đơn giản hóa điều này bằng cách ràng buộc tham số tuôn ra với một giá trị, thông qua functools.partial:

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

3
Chỉ cần tự hỏi, nhưng đó sẽ không phải là một trường hợp sử dụng hoàn hảo cho functools.partial?
0xC0000022L

Cảm ơn @ 0xC0000022L, điều này làm cho nó trông tốt hơn! print = functools.partial(print, flush=True)làm việc tốt cho tôi
MarSoft

@ 0xC0000022L thực sự, tôi đã cập nhật bài đăng để hiển thị tùy chọn đó, cảm ơn vì đã chỉ ra điều đó
Oliver

3
Nếu bạn muốn áp dụng ở mọi nơi,import builtins; builtins.print = partial(print, flush=True)
Perkins

4

Bạn cũng có thể sử dụng fcntl để thay đổi cờ tập tin đang bay.

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
Có một cửa sổ tương đương: stackoverflow.com/questions/881696/ Kẻ
Tobu

12
O_SYNC hoàn toàn không liên quan gì đến bộ đệm ở cấp độ người dùng mà câu hỏi này đang hỏi về.
apenwarr

4

Có thể ghi đè chỉ write phương thức sys.stdoutvới một cuộc gọi flush. Đề xuất thực hiện phương pháp dưới đây.

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

Giá trị mặc định của wđối số sẽ giữ writetham chiếu phương thức ban đầu . Sau khi write_flush được xác định, bản gốc writecó thể bị ghi đè.

stdout.write = write_flush

Mã giả định stdoutđược nhập theo cách này from sys import stdout.


3

Bạn có thể tạo một tệp không có bộ đệm và gán tệp này cho sys.stdout.

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

Bạn không thể thay đổi một cách kỳ diệu các thiết bị xuất chuẩn do hệ thống cung cấp; do hệ điều hành cung cấp cho chương trình python của bạn.


3

Biến thể hoạt động mà không gặp sự cố (ít nhất là trên win32; python 2.7, ipython 0.12) sau đó được gọi sau đó (nhiều lần):

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)

Bạn có chắc chắn đây không phải là bộ đệm?
lượng tử

1
Bạn có nên kiểm tra sys.stdout is sys.__stdout__thay vì dựa vào đối tượng thay thế có thuộc tính name không?
leewz

điều này hoạt động rất tốt nếu gunicorn không tôn trọng PYTHONUNBUFFERED vì một số lý do.
Brian Arsuaga

3

(Tôi đã đăng một bình luận, nhưng nó đã bị mất bằng cách nào đó. Vì vậy, một lần nữa :)

  1. Như tôi nhận thấy, CPython (ít nhất là trên Linux) hoạt động khác nhau tùy thuộc vào nơi đầu ra đi. Nếu nó đi đến một tty, thì đầu ra sẽ bị xóa sau mỗi ' \n'
    Nếu nó đi vào một đường ống / quy trình, thì nó được đệm và bạn có thể sử dụng các flush()giải pháp dựa trên hoặc tùy chọn -u được đề xuất ở trên.

  2. Liên quan một chút đến bộ đệm đầu ra:
    Nếu bạn lặp qua các dòng trong đầu vào với

    for line in sys.stdin:
    ...

sau đó, việc thực hiện trong CPython sẽ thu thập đầu vào trong một thời gian và sau đó thực thi thân vòng lặp cho một loạt các dòng đầu vào. Nếu tập lệnh của bạn chuẩn bị ghi đầu ra cho mỗi dòng đầu vào, thì tập lệnh này có thể trông giống như bộ đệm đầu ra nhưng thực tế nó đang tạo khối, và do đó, không có flush()kỹ thuật nào, v.v. sẽ giúp điều đó. Thật thú vị, bạn không có hành vi này trong pypy . Để tránh điều này, bạn có thể sử dụng

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


đây là nhận xét của bạn . Nó có thể là một lỗi trên các phiên bản Python cũ hơn. Bạn có thể cung cấp mã ví dụ? Một cái gì đó như for line in sys.stdinso vớifor line in iter(sys.stdin.readline, "")
jfs

cho dòng trong sys.stdin: print ("Line:" + line); sys.stdout.flush ()
tzp

nó trông giống như lỗi đọc trước . Nó chỉ xảy ra trên Python 2 và nếu stdin là một đường ống. Mã trong nhận xét trước đây của tôi cho thấy vấn đề ( for line in sys.stdincung cấp phản hồi chậm)
jfs

2

Một cách để có được đầu ra không có bộ đệm sẽ là sử dụng sys.stderrthay vì sys.stdouthoặc chỉ đơn giản gọi sys.stdout.flush()để rõ ràng buộc phải ghi.

Bạn có thể dễ dàng chuyển hướng mọi thứ được in bằng cách thực hiện:

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

Hoặc để chuyển hướng chỉ cho một printtuyên bố cụ thể :

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

Để thiết lập lại thiết bị xuất chuẩn, bạn chỉ cần làm:

sys.stdout = sys.__stdout__

1
Điều này có thể trở nên rất khó hiểu khi sau đó bạn cố gắng nắm bắt đầu ra bằng cách sử dụng chuyển hướng tiêu chuẩn và thấy bạn không thu được gì! ps stdout của bạn đang được in đậm và công cụ.
freespace

1
Một lưu ý lớn về việc in có chọn lọc lên stderr là điều này làm cho các dòng xuất hiện không đúng chỗ, vì vậy trừ khi bạn có dấu thời gian, điều này có thể rất khó hiểu.
haridsv
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.