禁用输出缓冲

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个回答

516

来自Magnus Lycka在邮件列表上的回答

You can skip buffering for a whole python process using python -u or by setting the environment variable PYTHONUNBUFFERED.

You could also replace sys.stdout with some other stream like wrapper which does a flush after every call.

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'

79
原始的sys.stdout仍然可用作sys.__stdout__。只是以防您需要它 =) - Antti Rasinen
54
#!/usr/bin/env python -u 无法工作!!请看这里 - wim
7
__getattr__ 是为了避免继承而存在的吗?! - Vladimir Keleshev
34
为了避免一些麻烦,以下是一些注意事项: 我注意到,输出缓冲方式取决于输出是进入tty还是另一个进程/管道。如果输出进入tty,则在每个\n后刷新缓冲区,但在管道中则会被缓冲。在后一种情况下,您可以利用这些刷新解决方案。 在Cpython(而不是pypy!!!)中:如果您使用**for line in sys.stdin:迭代输入…那么for循环将在运行循环体之前收集多行。虽然这样做更像批处理,但它的行为就像缓冲。相反,请使用while true: line = sys.stdin.readline()**。 - tzp
5
您可以使用iter()代替while循环:for line in iter(pipe.readline, ''):。在Python 3中,您不需要它,因为for line in pipe:尽可能快地生成结果。 - jfs
显示剩余10条评论

224

这对我来说更可取。缓冲发生是有原因的。完全禁用它会带来代价。 - Akaisteph7

90
# 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邮件列表的某个地方。


在Python 3中,你可以通过重写print函数的名称来使用带有flush的输出。尽管这是一个不太正当的技巧! - meawoppl
22
自Python 3.3起,您可以将print()函数的参数flush=True传递给它,以实现立即刷新输出。 - jfs
os.fdopen(sys.stdout.fileno(), 'wb', 0)(请注意,b表示二进制)和flush=True在3.6.4版本中均适用。但是,如果您正在使用subprocess启动另一个脚本,请确保您已经指定了python3,如果您安装了多个Python版本的话。 - not2qubit
1
@not2qubit: 如果你使用 os.fdopen(sys.stdout.fileno(), 'wb', 0),那么你将得到一个二进制文件对象,而不是 TextIO 流。你需要添加一个 TextIOWrapper 对象(确保启用 write_through 来消除所有缓冲区,或使用 line_buffering=True 只在换行时刷新)。 - Martijn Pieters
10
如果仅在换行符时刷新输出足够的话,从Python 3.7开始,您可以直接调用 sys.stdout.reconfigure(line_buffering=True) - Russell Davis
显示剩余2条评论

73

是的,可以。

你可以使用命令行上的"-u"开关来禁用它。

或者,你可以在每次写入时调用 sys.stdout 上的 .flush() 方法(或者用一个自动执行此操作的对象进行包装)。


-u在Python运行命令中最适合我! - undefined

40

这与Cristóvão D. Sousa的答案有关,但我还无法发表评论。

在Python 3中使用flush关键字参数的简单方法,以便始终具有无缓冲输出是:

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

随后,打印操作将始终直接刷新输出(除非给出flush=False)。

请注意,这只是部分回答问题,因为它不能重定向所有输出。但我想print是在Python中创建标准输出和错误输出的最常见方式,因此这两行可能涵盖了大多数用例。

请注意(b),它仅适用于定义它的模块/脚本。当编写模块时,这可能很好,因为它不会干扰sys.stdout

Python 2没有提供flush参数,但可以按照https://dev59.com/Robca4cB1Zd3GeqPc_s5#27991478中所述仿真类似Python 3的print函数。


2
在Python2中,除了没有flush关键字参数之外,其他都一样。 - o11c
@o11c,是的,你说得对。我确信我测试过了,但不知怎么回事,我似乎有些困惑(: 我修改了我的答案,希望现在没问题了。谢谢! - Tim

15
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中并不是一个好主意,因为这是无法释放的循环引用所在的位置,您可能需要检查这些引用。


3
如果旧的 stdout 仍然存在于 sys.__stdout__,正如一些人建议的那样,那么这个垃圾东西就不再必要了,对吧?尽管如此,这仍是一个很酷的技巧。 - Thomas Ahle
2
与@Federico的答案一样,这在Python 3中不起作用,因为调用print()时会抛出异常ValueError: can't have unbuffered text I/O - gbmhunter
你的“另一种可能性”起初似乎是最健壮的解决方案,但不幸的是,在你的sys.stdout.close()之后,另一个线程调用open()并在你的os.dup2(temp_fd, fileno)之前,它会出现竞争条件。当我尝试在ThreadSanitizer下使用你的技术时,我发现了这一点。由于dup2()在与open()同时发生竞争时会失败并显示EBUSY,因此这种故障变得更加明显。请参阅https://dev59.com/V2Ag5IYBdhLWcg3wlLqh。 - Don Hatch

15
以下的代码适用于 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)

2
运行两次就会在Windows上崩溃 :-) - Michael Clerx
1
@MichaelClerx 嗯,一定要记得关闭你的文件 xD。 - user3917838
在Raspbian 9上使用Python 3.5时,对于行sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg),会出现OSError: [Errno 29] Illegal seek的错误。 - sdbbs

14

在 Python 3 中,您可以对 print 函数进行monkey-patch操作,以始终发送 flush=True:

_orig_print = print

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

正如评论中指出的那样,您可以通过functools.partial将刷新参数绑定到一个值上,从而简化此操作:

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

3
这是否是使用functools.partial的完美示例呢? - 0xC0000022L
谢谢 @0xC0000022L,这样看起来更好了!print = functools.partial(print, flush=True) 对我很有效。 - MarSoft
@0xC0000022L,确实,我已经更新了帖子以显示该选项,感谢您指出。 - Oliver
4
如果您想要在任何地方应用这个功能,请执行以下操作:import builtins; builtins.print = partial(print, flush=True) - Perkins
奇怪的是,这种方法在Python 3.x中没有其他方法有效时起作用了,我想知道为什么其他记录的方法(使用-u标志)不起作用。 - truedat101

13

是的,它默认已启用。当调用Python时,您可以使用命令行上的-u选项来禁用它。


8
你也可以使用stdbuf工具来运行Python: stdbuf -oL python <script>
该命令将使Python脚本更加流畅。

True,但行缓冲是默认的(与tty一起),所以编写代码时假设输出是完全未缓冲的是否有意义 - 可能更好地显式print(..., end='', flush=True),在这种情况下很重要?另一方面,当几个程序同时写入相同的输出时,权衡往往从查看立即进度转向减少输出混乱,并且行缓冲变得有吸引力。因此,也许最好不编写显式的flush并在外部控制缓冲? - Beni Cherniavsky-Paskin
我认为不应该由程序员决定何时以及为什么调用flush,而是应该由进程本身来决定。在这里,外部缓冲控制是一种被迫的解决方法。 - dyomas

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