问题:有没有办法在没有获得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()
BrokenPipeError
正常,因为读取过程(头)终止并关闭管道的末端,而写入过程(python)仍然试图写入。
是一个异常情况,并且python脚本接收到BrokenPipeError
- 更确切地说,Python解释器接收它捕获的系统SIGPIPE信号并引发BrokenPipeError
以允许脚本处理错误。
并且你有效地可以处理错误,因为在你的上一个例子中,你只看到一条消息说该异常被忽略了 - 好吧它不是真的,但似乎与Python中的这个open issue有关:Python开发人员认为重要的是警告用户异常情况。
真正发生的是AFAIK python解释器总是在stderr上发出这个信号,即使你发现异常。但是你必须在退出之前关闭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()
根据Python文档,这在以下情况下抛出:
在另一端关闭的同时试图在管道上写字
这是因为head实用程序从stdout
读取,然后立即关闭它。
正如你所看到的,只需在每个sys.stdout.flush()
之后添加一个print()
就可以解决它。请注意,这有时在Python 3中不起作用。
您也可以像这样将它传送到awk
,以获得与head -3
相同的结果:
python3 0to3.py | awk 'NR >= 4 {exit} 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正确建议:在你身上你可以关闭write
和flush
函数,在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()
暂时忽略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)