禁用输出缓冲

664

Python解释器的sys.stdout默认启用输出缓冲吗?

如果答案是肯定的,有哪些禁用它的方法?

目前的建议:

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

在执行期间是否有其他方法以编程方式设置sys/sys.stdout中的某个全局标志?


如果您只想在使用print后刷新特定写入,参见如何刷新print函数的输出?


10
有关Python 3中的`print',请参见此答案 - Antti Haapala -- Слава Україні
2
我认为 -u 的缺点是它不能用于已编译的字节码或以 __main__.py 文件作为入口点的应用程序。 - akhan
1
完整的CPython初始化逻辑在这里:https://github.com/python/cpython/blob/v3.8.2/Python/pylifecycle.c#L1719-L1815 - Beni Cherniavsky-Paskin
16个回答

4
您可以创建一个非缓冲文件并将其分配给sys.stdout。
import sys 
myFile= open( "a.log", "w", 0 ) 
sys.stdout= myFile

您无法魔法般地更改系统提供的标准输出(stdout);因为它是由操作系统提供给您的Python程序的。


你也可以将 buffering=0 设置为 1 以进行行缓冲。 - Princy

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
有一个 Windows 的等价物:https://dev59.com/CnNA5IYBdhLWcg3wpfmu#881751 - Tobu
15
O_SYNC与这个问题所询问的用户空间缓冲完全无关。 - apenwarr

4

获取非缓冲输出的一种方法是使用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__

1
当你尝试使用标准重定向来捕获输出时,你可能会感到非常困惑,并发现你什么都没有捕获!顺便说一下,你的__stdout__正在被加粗处理。 - freespace
2
关于选择性地将内容打印到 stderr 的一个主要注意事项是,这样会导致行的位置不正确,所以除非您还有时间戳,否则这可能会变得非常混乱。 - haridsv

4

可以仅重写sys.stdoutwrite方法,让它调用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

我发现在Linux上,CPython的行为取决于输出去向。如果输出到终端,则每个'\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, "")的东西。 - jfs
for line in sys.stdin: print("Line: " +line); sys.stdout.flush() - tzp
看起来像是预读错误。这只会在Python 2上发生,如果stdin是一个管道。我之前评论中的代码演示了这个问题(for line in sys.stdin提供了延迟响应)。 - jfs
顺便说一句,根据isatty()函数,stdout的默认设置不同并不仅仅是Python的特性,这也是标准C库的行为。 - undefined

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)

你确定这不是缓存的吗? - quantum
1
你应该检查sys.stdout is sys.__stdout__而不是依赖于替换对象具有名称属性吗? - leewz
如果gunicorn由于某些原因无法尊重PYTHONUNBUFFERED,那么这很有效。 - Brian Arsuaga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接