47

问题:有没有办法在不获取的情况下使用flush=True该功能?print()BrokenPipeError

我有一个脚本pipe.py

for i in range(4000):
    print(i)

我在 Unix 命令行中这样称呼它:

python3 pipe.py | head -n3000

它返回:

0
1
2

这个脚本也是如此:

import sys
for i in range(4000):
    print(i)
    sys.stdout.flush()

但是,当我运行此脚本并将其通过管道传输到head -n3000

for i in range(4000):
    print(i, flush=True)

然后我得到这个错误:

    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

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

import sys
for i in range(4000):
    try:
        print(i, flush=True)
    except BrokenPipeError:
        sys.exit()
4

8 回答 8

53

BrokenPipeError是正常的,因为读取过程(头)终止并关闭其管道末端,而写入过程(python)仍在尝试写入。

Is一种异常情况,python 脚本接收到一个BrokenPipeError- 更准确地说,Python 解释器接收到一个系统 SIGPIPE 信号,它捕获并引发BrokenPipeError以允许脚本处理错误。

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

真正发生的是 AFAIK,python 解释器总是在 stderr 上发出信号,即使你捕获了异常。但是您只需要在退出之前关闭 stderr 即可消除该消息。

我将您的脚本稍微更改为:

  • 像在上一个示例中那样捕获错误
  • 捕获 IOError(我在 Windows64 上的 Python34 中得到)或 BrokenPipeError(在 FreeBSD 9.0 上的 Python 33 中) - 并为此显示一条消息
  • 在 stderr 上显示自定义的Done消息(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()

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

0
1
2
3
4
5
6
7
8
9
BrokenPipeError caught
Done

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

import sys

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

sys.stderr.close()
于 2014-11-04T15:20:30.100 回答
29

在 Python 3.7 文档中添加了一个注释SIGPIPE,它建议以这种方式捕获: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()

重要的是,它说:

不要为了避免而设置SIGPIPE的性情。这样做会导致您的程序在您的程序仍在写入时任何套接字连接被中断时意外退出。SIG_DFLBrokenPipeError

于 2019-10-23T06:56:42.303 回答
4

根据 Python 文档,在以下情况下会抛出此错误:

试图在另一端关闭时在管道上写

这是因为 head 实用程序从读取stdout然后立即关闭它

如您所见,只需在 eachsys.stdout.flush()之后添加一个即可解决此问题print()。请注意,这有时在 Python 3 中不起作用。

您也可以将其通过管道传输到awk这样的位置,以获得与以下相同的结果head -3

python3 0to3.py | awk 'NR >= 4 {exit} 1'

希望这有帮助,祝你好运!

于 2014-11-01T23:57:26.990 回答
2

我经常希望有一个命令行选项来抑制这些信号处理程序。

import signal

# Don't turn these signal into exceptions, just die.
signal.signal(signal.SIGINT, signal.SIG_DFL)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)

相反,我们能做的最好的事情就是在 Python 脚本开始运行时尽快卸载处理程序。

于 2019-09-07T12:43:07.117 回答
2

暂时忽略 SIGPPIE

我不确定这是多么糟糕的想法,但它确实有效:

#!/usr/bin/env python3

import signal
import sys

sigpipe_old = signal.getsignal(signal.SIGPIPE)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
for i in range(4000):
    print(i, flush=True)
signal.signal(signal.SIGPIPE, sigpipe_old)
于 2018-09-16T07:49:57.083 回答
1

正如您在已发布的输出中看到的那样,在析构函数阶段引发了最后一个异常:这就是您ignored最后拥有的原因

Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored

一个简单的例子来理解在这种情况下发生了什么:

>> class A():
...     def __del__(self):
...         raise Exception("It will be ignored!!!")
... 
>>> a = A()
>>> del a
Exception Exception: Exception('It will be ignored!!!',) in <bound method A.__del__ of <__builtin__.A instance at 0x7ff1d5c06d88>> ignored
>>> a = A()
>>> import sys
>>> sys.stderr.close()
>>> del a

在对象被销毁时触发的每个异常都会导致标准错误输出,解释异常发生和被忽略(这是因为 python 会通知您在销毁阶段无法正确处理某些事情)。无论如何,这种异常不能被缓存,因此您可以删除可以生成它的调用或 close stderr

回到问题上来。该异常不是真正的问题(因为它被忽略了)但是如果您不想打印它,则必须覆盖在对象将被销毁或关闭时可以调用的函数,stderr正如@SergeBallesta 正确建议的那样:在您的情况下您可以关闭 writeflush运行,并且在销毁上下文中不会触发任何异常

这是你如何做到这一点的一个例子:

import sys
def _void_f(*args,**kwargs):
    pass

for i in range(4000):
    try:
        print(i,flush=True)
    except (BrokenPipeError, IOError):
        sys.stdout.write = _void_f
        sys.stdout.flush = _void_f
        sys.exit()
于 2014-11-04T13:11:44.057 回答
1

虽然其他人已经详细介绍了潜在问题,但有一个简单的解决方法:

python whatever.py | tail -n +1 | head -n3000

说明:tail缓冲直到它的 STDIN 被关闭(python 退出并关闭它的 STDOUT)。所以当 head 退出时,只有 tail 获得 SIGPIPE。这-n +1实际上是一个无操作,使尾部输出从第 1 行开始的“尾部”,即整个缓冲区。

于 2019-07-31T19:17:02.250 回答
0

回答

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

解释

即使您捕获了BrokenPipeError异常,当您的程序退出并且 Python 尝试刷新标准输出时,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()

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

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

为什么(也许):将标准输出重定向到 /dev/null 实际上可能是某些人的正确解决方案,他们的程序在收到 BrokenPipeError 后将继续操作标准输出而不先检查它。但是,这不是常见的情况。

sys.stderr.close()

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

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

signal(SIGPIPE, SIG_DFL)

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

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

pipe.py | something | head

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

pipe.py | tee -p /dev/null | head

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

于 2021-11-04T00:06:15.093 回答