禁用输出缓冲


532

默认情况下,Python的解释器是否启用输出缓冲sys.stdout

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

到目前为止的建议:

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

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


7
对于Python 3中的“打印”,请参见此答案
Antti Haapala

1
我认为它的缺点-u是它不适用于已编译的字节码或以__main__.py文件为入口点的应用程序。
akhan

Answers:


443

Magnus Lycka的邮件列表中答复

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

您还可以将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'

71
原始sys.stdout仍可作为sys .__ stdout__获得。以防万一,您需要=)
Antti Rasinen

39
#!/usr/bin/env python -u不起作用!看到这里
wim 2012年

6
__getattr__只是为了避免继承?
弗拉基米尔·凯列谢夫

31
需要注意的一些注意事项:我注意到,输出缓冲的工作方式有所不同,具体取决于输出是进入tty还是其他进程/管道。如果使用tty,则在每个\ n之后将其刷新,但在管道中对其进行缓冲。在后一种情况下,您可以使用这些冲洗溶液。在Cpython中(不在pypy中!!!):如果您在sys.stdin中用for行遍历输入 ...然后for循环将在运行循环主体之前收集许多行。尽管它是批处理的,但其行为类似于缓冲。相反,请在true时执行:line = sys.stdin.readline()
tzp 2013年

5
@tzp:您可以使用iter()而不是while循环:for line in iter(pipe.readline, ''):。您不需要在Python 3上for line in pipe:尽快生成它。
jfs


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)

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


在Python3中,您可以使用冲洗代码覆盖打印函数的名称。虽然这是一个肮脏的把戏!
meawoppl 2014年

16
@meawoppl:自Python 3.3起,您可以将flush=True参数传递给print()函数。
jfs 2015年

编辑响应以显示响应在最新版本的python中无效
Mike

两者os.fdopen(sys.stdout.fileno(), 'wb', 0)(请注意b二进制),并flush=True在3.6.4中为我工作。但是,如果您正在使用子流程来启动另一个脚本,请确保已指定python3,如果您已安装多个python实例。
not2qubit

1
@ not2qubit:如果使用os.fdopen(sys.stdout.fileno(), 'wb', 0),最终将得到一个二进制文件对象,而不是TextIO流。您必须TextIOWrapper在混合中添加一个(确保启用write_through以消除所有缓冲区,或者line_buffering=True仅用于刷新换行符)。
马丁·彼得斯

55

是的。

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

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


19

这与克里斯托瓦·索萨(CristóvãoD. Sousa)的答案有关,但我无法发表评论。

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

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

之后,打印将始终直接刷新输出(flush=False给出的除外)。

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

注意(b)它仅在定义它的模块/脚本中起作用。编写模块时这可能会很好,因为它不会与混淆sys.stdout

Python 2不提供flush参数,但是您可以仿真Python 3型print函数,如此处https://stackoverflow.com/a/27991478/3734258所述


1
除了flushpython2中没有kwarg。
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()并不是幂等的,并且多次调用将导致如下错误:

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__像某些人建议的那样生活下去,那么就不需要垃圾了吧?不过这是一个很酷的把戏。
Thomas Ahle 2014年

1
与@Federico的答案一样,这不适用于Python 3,因为它ValueError: can't have unbuffered text I/O在调用时会引发异常print()
gbmhunter

起初,您的“另一种可能性”似乎是最可靠的解决方案,但不幸的是,在另一个线程在sys.stdout.close()之后和os.dup2(temp_fd,fileno)之前调用open()的情况下,它遇到了竞争问题)。当我尝试在ThreadSanitizer下使用您的技术时,我发现了这一点。当dup2()与open()像这样运行时,EUPY会失败,从而使失败更加严重。参见stackoverflow.com/questions/23440216/…–
唐·哈奇

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嗯,永远记得关闭文件xD。

Raspbian 9上的Python 3.5给了我OSError: [Errno 29] Illegal seek这一点sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs

12

是的,默认情况下启用。您可以在调用python时通过在命令行上使用-u选项禁用它。


7

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

stdbuf -oL python <script>


2
行缓冲(-oL启用时)仍在缓冲中-请参阅f / estackoverflow.com/questions/58416853/…,询问为什么end=''不再立即显示输出。
查尔斯·达菲,

的确如此,但是默认的行缓冲(带有tty),因此假设输出完全没有缓冲,编写代码有意义print(..., end='', flush=True)吗?也许最好在显着的地方显式地编写代码?OTOH,当多个程序同时写入相同的输出时,权衡往往从看到立即进展转向减少输出混淆,并且行缓冲变得有吸引力。因此,也许这最好不要写明确flush和控制缓冲外部?
贝尼·切尔尼亚夫斯基-帕斯金

我觉得不行。流程本身应该决定何时,为什么调用flush。外部缓冲控制是强制性的解决方法
dyomas

7

在Python 3中,您可以使用猴子补丁打印功能,以始终发送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实际上,我已经更新了帖子以显示该选项,感谢您指出
Oliver

2
如果您希望将其应用到任何地方,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
有一个等效的Windows:stackoverflow.com/questions/881696/…–
Tobu

12
O_SYNC与这个问题在询问的用户空间级缓冲完全无关。
apenwarr

4

可以使用调用的方法 覆盖的write方法。建议的方法实现如下。sys.stdoutflush

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

您无法神奇地更改系统提供的标准输出;因为它是由操作系统提供给您的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)

您确定没有缓冲吗?
2012年

1
您应该检查sys.stdout is sys.__stdout__而不是依靠具有name属性的替换对象吗?
leewz 2014年

如果Gunicorn由于某些原因不尊重PYTHONUNBUFFERED,则此方法非常有用。
Brian Arsuaga 2015年

3

(我发表了评论,但由于某种原因迷失了。因此,再次:)

  1. 正如我注意到的那样,CPython(至少在Linux上)的行为取决于输出的位置。如果是tty,则在每个'之后都将刷新输出。\n'
    如果是管道/进程,则将其缓冲,您可以使用flush()基于基础的解决方案或上面建议的-u选项。

  2. 与输出缓冲稍微相关:
    如果您使用来遍历输入中的行

    for line in sys.stdin:
    ...

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

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


这是您的评论。这可能是旧版Python上的错误。您能提供示例代码吗?像for line in sys.stdinvs.for line in iter(sys.stdin.readline, "")
jfs

对于sys.stdin中的行:print(“ Line:” + line); sys.stdout.flush()
2013年

它看起来像是预读错误。如果stdin是管道,则仅应在Python 2上发生。我之前的评论中的代码演示了该问题(for line in sys.stdin提供了延迟的响应)
jfs 2015年

2

获得无缓冲输出的一种方法是使用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
当您以后尝试使用标准重定向捕获输出,却发现您什么也没捕获时,这可能会造成很大的混乱。ps你的标准输出被加粗了。
自由空间

1
关于有选择地打印到stderr的一个大注意事项是,这会导致行显得不适当,因此,除非您也有时间戳记,否则这可能会造成混乱。
haridsv 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.