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

时间:2014-11-01 19:27:15

标签: python unix python-3.x flush broken-pipe

问题:有没有办法flush=True print()功能使用pipe.py而无法获取BrokenPipeError

我有一个脚本for i in range(4000): print(i)

python3 pipe.py | head -n3000

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

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()

{{1}}

7 个答案:

答案 0 :(得分:22)

BrokenPipeError是正常的,因为读取过程(head)终止并关闭管道的末尾,而写入过程(python)仍然试图写入。

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

并且您可以有效地处理错误,因为在上一个示例中,您只看到一条消息说该异常被忽略 - 确定它不是真的,但似乎与Python中的open issue相关: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()

这里是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()

答案 1 :(得分:4)

根据Python文档,在以下情况下抛出此内容:

  

尝试在管道上写入而另一端已经关闭

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

正如您所看到的,只需在每sys.stdout.flush()之后添加print()即可解决此问题。请注意,这有时在Python 3中不起作用。

您可以选择将其传递给awk,以获得与head -3相同的结果:

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

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

答案 2 :(得分:2)

在Python 3.7文档中,note on SIGPIPEadded,它建议以这种方式捕获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_DFL,以避免BrokenPipeError。这样做还会导致在程序仍在对其进行写操作时任何套接字连接中断时,程序也会异常退出。

答案 3 :(得分: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会通知您在破坏阶段无法正确处理某些内容)。无论如何,这种异常无法缓存,因此您可以删除可以生成它或关闭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()

答案 4 :(得分:0)

暂时忽略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)

答案 5 :(得分:0)

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

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

说明:tail缓冲直到其STDIN关闭(python退出并关闭其STDOUT)。因此,当头部退出时,只有尾巴会获得SIGPIPE。 -n +1实际上是一个空操作,使尾部输出从第1行开始即整个缓冲区成为尾部。

答案 6 :(得分:0)

我通常希望有一个命令行选项来禁止这些信号处理程序。

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脚本开始运行后尽快卸载处理程序。