禁用输出缓冲

Posted

技术标签:

【中文标题】禁用输出缓冲【英文标题】:Disable output buffering 【发布时间】:2010-09-11 14:01:43 【问题描述】:

在 Python 的解释器中是否默认为 sys.stdout 启用输出缓冲?

如果答案是肯定的,有什么方法可以禁用它?

目前的建议:

    使用-u 命令行开关 将 sys.stdout 包装在每次写入后刷新的对象中 设置PYTHONUNBUFFERED环境变量 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

有没有其他方法可以在执行期间以编程方式在sys/sys.stdout 中设置一些全局标志?

【问题讨论】:

对于 Python 3 中的“打印”,请参阅this answer。 我认为-u 的一个缺点是它不适用于已编译的字节码或以__main__.py 文件作为入口点的应用程序。 完整的 CPython 初始化逻辑在这里:github.com/python/cpython/blob/v3.8.2/Python/… 【参考方案1】:

来自Magnus Lycka answer on a mailing list:

您可以跳过整个缓冲 使用“python -u”的python进程 (或#!/usr/bin/env python -u 等)或通过 设置环境变量 蟒蛇无缓冲。

您也可以将 sys.stdout 替换为 其他一些流,如包装器 每次调用后都会刷新。

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'

【讨论】:

原始 sys.stdout 仍可作为 sys.__stdout__ 使用。以防万一你需要它=) #!/usr/bin/env python -u 不起作用!!见here __getattr__ 只是为了避免继承?! 一些注意事项可以省去一些麻烦:正如我所注意到的,输出缓冲的工作方式不同,具体取决于输出是到 tty 还是另一个进程/管道。如果它进入一个 tty,那么它在每个 \n 之后被刷新,但在管道中它被缓冲。在后一种情况下,您可以使用这些冲洗溶液。在 Cpython 中(不是在 pypy 中!!!):如果您使用 for line in sys.stdin: 遍历输入...那么 for 循环将在主体之前收集一些行循环运行。这将表现得像缓冲,尽管它是批处理。相反,while true: line = sys.stdin.readline() @tzp:您可以使用 iter() 代替 while 循环:for line in iter(pipe.readline, ''):。在 for line in pipe: 尽快产生的 Python 3 上,您不需要它。【参考方案2】:

我宁愿将我的答案放在How to flush output of print function? 或Python's print function that flushes the buffer when it's called? 中,但由于它们被标记为与这个重复(我不同意),所以我会在这里回答。

从 Python 3.3 开始,print() 支持关键字参数“flush”(see documentation):

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

【讨论】:

【参考方案3】:
# 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)

致谢:“Sebastian”,在 Python 邮件列表中的某处。

【讨论】:

在 Python3 中,您可以使用刷新函数覆盖打印函数的名称。这是一个肮脏的把戏! @meawoppl:从 Python 3.3 开始,您可以将flush=True 参数传递给print() 函数。 编辑响应以显示响应在最新版本的python中无效 @not2qubit:如果你使用os.fdopen(sys.stdout.fileno(), 'wb', 0),你最终会得到一个二进制文件对象,而不是TextIO 流。您必须添加 TextIOWrapper 到组合中(确保启用 write_through 以消除所有缓冲区,或使用 line_buffering=True 仅刷新换行符)。 如果在换行符上刷新就足够了,从 Python 3.7 开始,您可以简单地调用 sys.stdout.reconfigure(line_buffering=True)【参考方案4】:

是的。

您可以在命令行中使用“-u”开关禁用它。

或者,您可以在每次写入时在 sys.stdout 上调用 .flush()(或用自动执行此操作的对象包装它)

【讨论】:

【参考方案5】:

这与 Cristóvão D. Sousa 的回答有关,但我还不能发表评论。

使用 Python 3flush 关键字参数以始终具有无缓冲输出的直接方法是:

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

之后,print 总是会直接刷新输出(flush=False 除外)。

请注意,(a) 这只是部分回答了问题,因为它不会重定向所有输出。但我猜print 是在 python 中创建输出到stdout/stderr 的最常用方法,所以这两行可能涵盖了大部分用例。

注意 (b) 它只适用于您定义它的模块/脚本。这在编写模块时会很好,因为它不会与sys.stdout 混淆。

Python 2 不提供 flush 参数,但您可以模拟 Python 3 类型的 print 函数,如此处所述 https://***.com/a/27991478/3734258。

【讨论】:

除了python2中没有flushkwarg。 @o11c ,是的,你是对的。我确定我测试了它,但不知何故我似乎很困惑(:我修改了我的答案,希望现在没问题。谢谢!【参考方案6】:
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()就不是幂等的,多次调用会报这样的错误:

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 并不是一个好主意,因为它是放置不可释放循环的地方,您可能需要检查这些。)

【讨论】:

如果旧的stdout 仍然像某些人建议的那样存在于sys.__stdout__ 上,那么垃圾就没有必要了,对吧?不过,这是一个很酷的技巧。 与@Federico 的回答一样,这不适用于Python 3,因为它会在调用print() 时抛出异常ValueError: can't have unbuffered text I/O 您的“另一种可能性”乍一看似乎是最强大的解决方案,但不幸的是,如果另一个线程在您的 sys.stdout.close() 之后和之前调用 open() ,它就会遇到竞争条件你的 os.dup2(temp_fd,fileno)。当我尝试在 ThreadSanitizer 下使用您的技术时,我发现了这一点,它就是这样做的。 dup2() 在与 open() 竞争时因 EBUSY 失败而使失败更加响亮;见***.com/questions/23440216/…【参考方案7】:

以下适用于 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 上崩溃:-) @MichaelClerx 嗯嗯,永远记得关闭你的文件 xD。 Raspbian 9 上的 Python 3.5 给了我 OSError: [Errno 29] Illegal seek 的行 sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)【参考方案8】:

是的,默认启用。你可以在调用 python 时在命令行中使用 -u 选项来禁用它。

【讨论】:

【参考方案9】:

在 Python 3 中,您可以对 print 函数进行猴子修补,以始终发送 flush=True:

_orig_print = print

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

正如评论中指出的,您可以通过 functools.partial 将 flush 参数绑定到一个值来简化此操作:

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

【讨论】:

只是想知道,但这不是functools.partial 的完美用例吗? 感谢@0xC0000022L,这使它看起来更好! print = functools.partial(print, flush=True) 对我来说很好。 @0xC0000022L 确实,我已经更新了帖子以显示该选项,感谢您指出这一点 如果你想让它在任何地方都适用,import builtins; builtins.print = partial(print, flush=True) 奇怪的是,这种方法在 Python 3.x 没有其他方法时有效,我想知道为什么其他记录的方法(使用 -u 标志)不起作用。【参考方案10】:

您还可以使用stdbuf 实用程序运行 Python:

stdbuf -oL python &lt;script&gt;

【讨论】:

行缓冲(-oL 启用)仍在缓冲 - 参见 f/e ***.com/questions/58416853/…,询问为什么 end='' 使输出不再立即显示。 是的,但是行缓冲是默认设置(带有 tty),所以假设输出完全没有缓冲,编写代码是否有意义——也许最好明确地print(..., end='', flush=True) 这很重要? OTOH,当多个程序同时写入相同的输出时,权衡往往会从看到即时进展转向减少输出混淆,并且行缓冲变得有吸引力。所以也许最好不要写明确的flush 并在外部控制缓冲? 我想,不。进程本身应该决定何时以及为何调用flush。此处强制使用外部缓冲控制解决方法【参考方案11】:

您还可以使用 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)

【讨论】:

有一个windows等价物:***.com/questions/881696/… O_SYNC 与此问题所询问的用户空间级缓冲完全无关。【参考方案12】:

您可以创建一个无缓冲文件并将此文件分配给 sys.stdout。

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

你不能神奇地改变系统提供的标准输出;因为它是由操作系统提供给你的 python 程序的。

【讨论】:

你也可以设置buffering=1而不是0进行行缓冲。【参考方案13】:

可以用调用flush 的方法覆盖sys.stdoutwrite 方法。建议的方法实现如下。

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

【讨论】:

【参考方案14】:

获得无缓冲输出的一种方法是使用 sys.stderr 而不是 sys.stdout 或简单地调用 sys.stdout.flush() 以显式强制写入。

您可以通过以下方式轻松重定向打印的所有内容:

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

或仅针对特定的print 语句进行重定向:

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

要重置标准输出,您可以这样做:

sys.stdout = sys.__stdout__

【讨论】:

当您稍后尝试使用标准重定向捕获输出时,这可能会变得非常混乱,却发现您什么也没有捕获! p.s.你的 stdout 被加粗等等。 关于选择性打印到 stderr 的一大注意事项是,这会导致线条显得不合适,因此除非您也有时间戳,否则这可能会变得非常混乱。【参考方案15】:

在不崩溃的情况下工作的变体(至少在 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)

【讨论】:

你确定这没有缓冲吗? 您是否应该检查sys.stdout is sys.__stdout__ 而不是依赖具有名称属性的替换对象? 如果 gunicorn 出于某种原因不尊重 PYTHONUNBUFFERED,这将非常有用。【参考方案16】:

(我发表了一条评论,但它不知何故丢失了。所以,再次:)

    我注意到,CPython(至少在 Linux 上)的行为取决于输出的位置。如果它进入 tty,则在每个 '\n' 之后刷新输出 如果它进入管道/进程,那么它会被缓冲,您可以使用基于 flush() 的解决方案或上面推荐的 -u 选项。

    与输出缓冲略有关系: 如果您使用

    遍历输入中的行

    for line in sys.stdin: ...

然后 CPython 中的 for 实现将收集输入一段时间,然后为一堆输入行执行循环体。如果您的脚本要为每个输入行写入输出,这可能看起来像输出缓冲,但实际上是批处理,因此,flush() 等技术都无济于事。 有趣的是,您在 pypy 中没有这种行为。 为避免这种情况,您可以使用

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

【讨论】:

here's your comment。这可能是旧 Python 版本的错误。你能提供示例代码吗? for line in sys.stdinfor line in iter(sys.stdin.readline, "") 之类的东西 对于 sys.stdin 中的行: print("Line: " +line); sys.stdout.flush() 它看起来像the read-ahead bug。它应该只发生在 Python 2 上并且如果 stdin 是一个管道。我之前评论中的代码演示了这个问题(for line in sys.stdin 提供了延迟响应)

以上是关于禁用输出缓冲的主要内容,如果未能解决你的问题,请参考以下文章

暂时禁用Java图形小程序中的双缓冲

在重定向的stdout管道上禁用缓冲(Win32 API,C ++)

使用 linux 帧缓冲区进行图形但禁用控制台文本

在 Firefox(或其他浏览器)中禁用接收缓冲区

Primefaces:禁用数据缓冲区的ajaxStatus

将OpenGL帧缓冲区对象与Qt(QOpenGLWidget)一起使用,绘制到帧缓冲区时如何禁用多重采样