在Python中进行刷新时如何防止BrokenPipeError?

tom*_*sen 26 python unix flush broken-pipe python-3.x

问题:有没有办法使用flush=Trueprint()功能而无法获得BrokenPipeError

我有一个脚本pipe.py:

for i in range(4000):
    print(i)
Run Code Online (Sandbox Code Playgroud)

我从Unix命令行这样称呼它:

python3 pipe.py | head -n3000
Run Code Online (Sandbox Code Playgroud)

它返回:

0
1
2
Run Code Online (Sandbox Code Playgroud)

这个脚本也是如此:

import sys
for i in range(4000):
    print(i)
    sys.stdout.flush()
Run Code Online (Sandbox Code Playgroud)

但是,当我运行此脚本并将其传递给head -n3000:

for i in range(4000):
    print(i, flush=True)
Run Code Online (Sandbox Code Playgroud)

然后我收到这个错误:

    print(i, flush=True)
BrokenPipeError: [Errno 32] Broken pipe
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored
Run Code Online (Sandbox Code Playgroud)

我也试过下面的解决方案,但我仍然得到BrokenPipeError:

import sys
for i in range(4000):
    try:
        print(i, flush=True)
    except BrokenPipeError:
        sys.exit()
Run Code Online (Sandbox Code Playgroud)

Ser*_*sta 28

BrokenPipeError作为所述幻象,因为读取过程(头)终止,而写入处理(蟒)仍试图写入关闭其在管的端部是正常的.

是否异常情况,和Python脚本接收BrokenPipeError-更确切地说,Python解释器接收到它捕获并提高系统SIGPIPE信号BrokenPipeError,以允许脚本来处理错误.

并且您可以有效地处理错误,因为在上一个示例中,您只看到一条消息说该异常被忽略 - 确定它不是真的,但似乎与Python中的这个开放问题有关:Python开发人员认为重要的是警告用户异常情况.

真正发生的是AFAIK python解释器总是在stderr上发出这个信号,即使你发现异常.但是你必须在退出之前关闭stderr以消除消息.

我稍微改变了你的脚本:

  • 像上一个例子中那样捕获错误
  • 捕获IOError(我在Windows64上的Python34中获取)或BrokenPipeError(在FreeBSD 9.0上的Python 33中) - 并显示一条消息
  • 在stderr上显示自定义完成消息(由于管道损坏,stdout已关闭)
  • 在退出之前关闭stderr以消除该消息

这是我使用的脚本:

import sys

try:
    for i in range(4000):
            print(i, flush=True)
except (BrokenPipeError, IOError):
    print ('BrokenPipeError caught', file = sys.stderr)

print ('Done', file=sys.stderr)
sys.stderr.close()
Run Code Online (Sandbox Code Playgroud)

这里的结果是python3.3 pipe.py | head -10:

0
1
2
3
4
5
6
7
8
9
BrokenPipeError caught
Done
Run Code Online (Sandbox Code Playgroud)

如果您不想使用无关的消息,请使用:

import sys

try:
    for i in range(4000):
            print(i, flush=True)
except (BrokenPipeError, IOError):
    pass

sys.stderr.close()
Run Code Online (Sandbox Code Playgroud)

  • 请注意,即使您捕获到异常,Python 3也会添加一条消息!!!`忽略异常:&lt;_io.TextIOWrapper名称='&lt;stdout&gt;'模式='w'编码='UTF-8'&gt;`/sf/ask/1142002501/ ignore-message-in-python-3在Python 3.6.8中进行了测试。 (2认同)

sko*_*kin 26

一个音符在SIGPIPE添加的Python 3.7文档,并建议赶上BrokenPipeError这样:

import os
import sys

def main():
    try:
        # simulate large output (your code replaces this loop)
        for x in range(10000):
            print("y")
        # flush output here to force SIGPIPE to be triggered
        # while inside this try block.
        sys.stdout.flush()
    except BrokenPipeError:
        # Python flushes standard streams on exit; redirect remaining output
        # to devnull to avoid another BrokenPipeError at shutdown
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())
        sys.exit(1)  # Python exits with error code 1 on EPIPE

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

重要的是,它说:

请勿设置为避免设置SIGPIPE的配置。这样做会导致您的程序意外退出,只要您的程序仍在写入时任何套接字连接被中断。SIG_DFLBrokenPipeError

  • 这似乎是最新的答案,也是官方文档中推荐的方法。在 Mac OS 10.14 / Python 3.7.5 上测试 (2认同)

hac*_*rb9 6

回答

import sys
for i in range(4000):
    try:
        print(i, flush=True)
    except BrokenPipeError:
        sys.stdout = None
Run Code Online (Sandbox Code Playgroud)

解释

即使您捕获了BrokenPipeError异常,当您的程序退出并且 Python 尝试刷新 stdout 时,Python 也会再次抛出该异常。通过将 stdout 设置为 None,Python 将不会尝试刷新它。

缺点

虽然 Python 例程(例如 )print()可以正确检查 stdout 是否为 None 并且不会失败,但不检查的程序并不罕见。如果你的程序在将 stdout 设置为 None 后尝试使用stdout.write()或类似的内容,那么 Python 将抛出 AttributeError。

其他答案(以及为什么不)

没有哪个答案比 更短或更简单sys.stdout = None,但一些常见答案存在重大问题。

/dev/null

Python 开发人员有自己的建议代码来处理 BrokenPipeError。

import os
import sys

def main():
    try:
        # simulate large output (your code replaces this loop)
        for x in range(10000):
            print("y")
        # flush output here to force SIGPIPE to be triggered
        # while inside this try block.
        sys.stdout.flush()
    except BrokenPipeError:
        # Python flushes standard streams on exit; redirect remaining output
        # to devnull to avoid another BrokenPipeError at shutdown
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())
        sys.exit(1)  # Python exits with error code 1 on EPIPE

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

虽然这是规范的答案,但它相当奇怪,因为它不必要地向 /dev/null 打开一个新的文件描述符,以便 Python 可以在关闭之前刷新它。

为什么不呢:对于大多数人来说,这是毫无意义的。这个问题是由 Python 刷新我们已经捕获到 BrokenPipeError 的句柄引起的。我们知道它会失败,因此 Python 的解决方案应该是不刷新该句柄。仅仅为了安抚Python而分配一个新的文件描述符是愚蠢的。

为什么(也许):对于某些人来说,将 stdout 重定向到 /dev/null 实际上可能是正确的解决方案,这些人的程序在收到 BrokenPipeError 后将继续操作 stdout,而不先检查它。然而,这种情况并不常见。

sys.stderr.close()

有些人建议关闭 stderr 以隐藏伪造的 BrokenPipe 错误消息。

为什么不呢:它还可以防止显示任何合法错误。

signal(SIGPIPE, SIG_DFL)

另一个常见的答案是使用SIG_DFL默认信号处理程序,使程序在收到 SIGPIPE 信号时终止。

为什么不呢:SIGPIPE 可以针对任何文件描述符发送,而不仅仅是 stdout,因此,如果您的整个程序正在写入连接被中断的网络套接字,那么您的整个程序将突然神秘地死亡。

pipe.py | something | head

一种非 Python 解决方案是首先将 stdout 通过管道传输到一个程序,即使该程序自己的标准输出已关闭,该程序也会继续从 Python 程序读取数据。例如,假设您有 GNU 版本的tee,这可以工作:

pipe.py | tee -p /dev/null | head
Run Code Online (Sandbox Code Playgroud)

为什么不:这个问题是在 Python 中寻找答案。此外,它也不是最理想的,因为它会使 pipeline.py 的运行时间超过其需要的时间,可能会消耗大量资源。