Как предотвратить BrokenPipeError при выполнении Флеша в Python?
вопрос: есть ли способ, чтобы использовать flush=True
на без 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 ответов
на BrokenPipeError
нормально, как сказал Фантом, потому что процесс чтения (head) завершается и закрывает свой конец трубы, пока процесс записи (python) все еще пытается писать.
Is и аномальное состояние, и скрипты python получают BrokenPipeError
- точнее, интерпретатор Python получает системный сигнал SIGPIPE, который он ловит и поднимает BrokenPipeError
чтобы скрипт мог обработать ошибку.
и вы можете эффективно обрабатывать ошибка, потому что в вашем последнем примере вы видите только сообщение о том, что исключение было проигнорировано - ok это не правда, но, похоже, связано с этим открыть вопрос в Python: разработчики Python считают важным предупредить пользователя о ненормальном состоянии.
на самом деле происходит то, что AFAIK интерпретатор python всегда сигнализирует об этом на stderr, даже если вы поймаете исключение. Но вам просто нужно закрыть stderr перед выходом, чтобы избавиться от сообщения.
I слегка изменил свой скрипт на:
- поймать ошибку, как вы сделали в последнем примере
- поймайте либо IOError (который я получаю в Python34 на Windows64), либо BrokenPipeError (в Python 33 на FreeBSD 9.0) - и отобразите сообщение для этого
- отображение пользовательского сделал сообщение на 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()
согласно документации Python, это выбрасывается, когда:
попытка написать на трубе, в то время как другой конец был закрыт
это связано с тем, что главная утилита читает из 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
функция и никакое исключение не будет вызвано в контексте уничтожения
это пример того, как вы можете это сделать:
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()
временно игнорировать 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)