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

问题描述 投票:21回答:4

问题:有没有办法在没有获得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()
python unix python-3.x flush broken-pipe
4个回答
24
投票

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

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

并且你有效地可以处理错误,因为在你的上一个例子中,你只看到一条消息说该异常被忽略了 - 好吧它不是真的,但似乎与Python中的这个open issue有关:Python开发人员认为重要的是警告用户异常情况。

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

我稍微改变了你的脚本:

  • 像上一个例子中那样捕获错误
  • 捕获IOError(我在Windows 64上的Python34中获得)或Broken Pipe Error(在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()

5
投票

根据Python文档,这在以下情况下抛出:

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

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

正如你所看到的,只需在每个sys.stdout.flush()之后添加一个print()就可以解决它。请注意,这有时在Python 3中不起作用。

您也可以像这样将它传送到awk,以获得与head -3相同的结果:

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

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


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函数,在destroy上下文中不会触发任何异常

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

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

0
投票

暂时忽略SIGPIPE

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

#!/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)
© www.soinside.com 2019 - 2024. All rights reserved.