Как предотвратить BrokenPipeError при выполнении флеша в Python?
Вопрос: Есть ли способ использовать flush=True
для функции print()
без получения BrokenPipeError
?
У меня есть script pipe.py
:
for i in range(4000):
print(i)
Я вызываю это так из командной строки Unix:
python3 pipe.py | head -n3000
И он возвращает:
0
1
2
Так делает это script:
import sys
for i in range(4000):
print(i)
sys.stdout.flush()
Однако, когда я запускаю этот script и подключаю его к 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
BrokenPipeError
является нормальным, так как указанный phantom, потому что процесс чтения (head) завершает и закрывает свой конец канала, пока процесс записи (python) все еще пытается записать.
Является ненормальным условием, а скрипты python получают BrokenPipeError
- точнее, интерпретатор Python получает сигнал SIGPIPE системы, который он ловит и поднимает BrokenPipeError
, чтобы разрешить script для обработки ошибки.
И вы действительно можете обработать ошибку, потому что в вашем последнем примере вы видите только сообщение о том, что исключение было проигнорировано - нормально, это неверно, но, похоже, связано с этим открытая проблема в Python: разработчики Python считают важным предупредить пользователя об аномальном состоянии.
Что действительно происходит, так это то, что AFAIK интерпретатор python всегда сигнализирует об этом на stderr, даже если вы поймаете исключение. Но вам просто нужно закрыть stderr перед выходом, чтобы избавиться от сообщения.
Я немного изменил ваш script на:
- поймать ошибку, как это было в последнем примере
- поймать IOError (который я получаю в Python34 на Windows64) или BrokenPipeError (в Python 33 на FreeBSD 9.0) - и отобразить сообщение для этого
- отображает пользовательское сообщение Done на stderr (stdout закрывается из-за сломанной трубы)
- закрыть stderr перед выходом, чтобы избавиться от сообщения
Вот script я использовал:
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()
Ответ 2
В соответствии с документацией Python это бросается, когда:
пытается записать на трубку, пока другой конец закрыт
Это связано с тем, что головная утилита читает от stdout
, а затем быстро закрывает ее.
Как вы можете видеть, его можно обойти, просто добавив sys.stdout.flush()
после каждого print()
. Обратите внимание, что это иногда не работает в Python 3.
Вы можете в качестве альтернативы передать его на awk
следующим образом, чтобы получить тот же результат, что и head -3
:
python3 0to3.py | awk 'NR >= 4 {exit} 1'
Надеюсь, это помогло, удачи!
Ответ 3
Как вы можете видеть в выводе, которое вы опубликовали, последнее исключение возникает в фазе деструктора: вот почему у вас есть 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()
Ответ 4
Игнорировать 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
В то время как другие подробно рассмотрели основную проблему, существует простой обходной путь:
python whatever.py | tail -n +1 | head -n3000
Объяснение: tail
буферизует до тех пор, пока STDIN не будет закрыт (python завершает работу и закрывает свой STDOUT). Так что только хвост получает SIGPIPE при выходе из головы. -n +1
, по сути, не используется, поэтому хвост выводит "хвост", начиная со строки 1, то есть всего буфера.
Ответ 6
Мне часто хотелось, чтобы была опция командной строки для подавления этих обработчиков сигналов.
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 начнет работать.