Как предотвратить BrokenPipeError при сбросе в Python?


person tommy.carstensen    schedule 01.11.2014    source источник
comment
Я не могу воспроизвести это на OS X 10.10, сейчас пробую CentOS 6.6.   -  person jgritty    schedule 01.11.2014
comment
Я только что попробовал OS X 10.9.4, и мне не удалось его воспроизвести. Я получил ошибку на Ubuntu 12.04.2 LTS. Попробую на Linux Mint Qiana.   -  person tommy.carstensen    schedule 01.11.2014
comment
У меня ломаются все ваши скрипты, кроме первого...   -  person phantom    schedule 02.11.2014
comment
Я не могу воспроизвести исключение, запустив ваш скрипт в Python 3.4.1. Какую версию Python вы используете?   -  person user4815162342    schedule 04.11.2014
comment
Я пробовал версию 3.4.1 в Mac OS X 10.9.4 и версию 3.3.2 в Ubuntu 12.04.2 LTS. В настоящее время я не могу воспроизвести ошибку самостоятельно. Мне пришлось изменить диапазон (4) на диапазон (4000) и head -n3 на head -n3000, чтобы воспроизвести ошибку.   -  person tommy.carstensen    schedule 04.11.2014


Ответы (7)


BrokenPipeError является нормальным, как упомянутый фантом, потому что процесс чтения (голова) завершается и закрывает свой конец канала, в то время как процесс записи (python) все еще пытается писать.

является ненормальным состоянием, и скрипты Python получают BrokenPipeError — точнее, интерпретатор Python получает системный сигнал SIGPIPE, который он перехватывает и поднимает BrokenPipeError, чтобы скрипт мог обработать ошибку.

И вы эффективно можете обработать ошибку, потому что в вашем последнем примере вы видите только сообщение о том, что исключение было проигнорировано — хорошо, это неправда, но похоже, что это связано с этим открытая проблема в Python: разработчики Python считают важным предупредить пользователя о ненормальном состоянии.

Что на самом деле происходит, так это то, что интерпретатор python всегда сигнализирует об этом на stderr, даже если вы поймаете исключение. Но вам просто нужно закрыть stderr перед выходом, чтобы избавиться от сообщения.

Я немного изменил ваш скрипт на:

  • поймать ошибку, как в последнем примере
  • поймать либо IOError (которую я получаю в Python34 на Windows64), либо BrokenPipeError (в Python 33 на FreeBSD 9.0) — и отобразить сообщение для этого
  • отображать пользовательское сообщение Done на стандартном потоке (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()
person Serge Ballesta    schedule 04.11.2014
comment
Вообще очень круто :) Спасибо! Также не знал, что в кортеж можно помещать исключения. Спасибо, что показали мне и это. Это работает для 3.3.2 и 3.4.0 в Mac OS X 10.9.4 и Ubuntu 12.04.2 LTS (все 4 комбинации). Кажется, он работает как с BrokenPipeError, так и с IOError самостоятельно; обе части OSError в ‹a href=docs.python.org /3/библиотека/ иерархия исключений‹/a›. - person tommy.carstensen; 05.11.2014
comment
Обратите внимание, что Python 3 добавляет сообщение, даже если вы ловите исключение!!! Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> stackoverflow.com/questions/16314321/ Протестировано в Python 3.6.8. - person Ciro Santilli 新疆再教育营六四事件ۍ 18.06.2019

примечание к SIGPIPE было добавлен в документацию Python 3.7, и он рекомендует поймать 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. Это приведет к неожиданному выходу вашей программы также всякий раз, когда любое соединение сокета прерывается, пока ваша программа все еще записывает в него.

person skovorodkin    schedule 23.10.2019
comment
Кажется, это самый актуальный ответ, а также подход, рекомендованный в официальной документации. Протестировано на Mac OS 10.14/Python 3.7.5 - person Mailson; 18.11.2019
comment
Интересно, будет ли передача flush=True в print иметь тот же эффект, что и строка sys.stdout.flush(). - person Alex Povel; 23.03.2021
comment
А если я не хочу выходить? - person topkek; 25.03.2021

Согласно документации Python, это вызывается, когда:

пытаюсь писать на трубе, пока другой конец закрыт

Это связано с тем, что утилита head читает из stdout, затем быстро закрывает его.

Как видите, это можно обойти, просто добавив sys.stdout.flush() после каждого print(). Обратите внимание, что иногда это не работает в Python 3.

В качестве альтернативы вы можете подключить его к awk, чтобы получить тот же результат, что и head -3:

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

Надеюсь, это помогло, удачи!

person phantom    schedule 01.11.2014
comment
Спасибо за это. К сожалению, обходной путь awk не вариант. Я не знаю, сколько строк выдает реальный вывод. Я оставлю вопрос без ответа на несколько дней, если вы не возражаете. Спасибо еще раз. - person tommy.carstensen; 02.11.2014
comment
@tommy.carstensen sys.stdout.flush() не работает? Кроме того, с head вам также не нужно знать, сколько строк будет в любом случае? - person phantom; 02.11.2014
comment
@tommy.carstensen Невозможно сделать то, что вы просите. Не существует обходного пути, позволяющего head работать с вашей программой таким образом. - person phantom; 02.11.2014
comment
@tommy.carstensen Этого не будет, потому что это зависит от программы, в которую вы передаете вывод. Причина, по которой возникает эта ошибка, заключается в том, что когда программа, в которую вы передаете вывод, закрывается до вашего скрипта python, она закрывает выходной канал, например. стандартный выход. Вам лучше либо вручную подключиться к subprocess, либо использовать возможности регулярных выражений Python вместо grep. - person phantom; 02.11.2014
comment
@tommy.carstensen Я сосредоточен на этом. Я говорю вам, что это невозможно. Цитирую The reason this error occurs is because when the program you pipe output into closes before your python script, it closes the output pipe, e.g. stdout. - person phantom; 02.11.2014
comment
@tommy.carstensen Нет проблем! Можно, но не так просто, к сожалению :( - person phantom; 02.11.2014

Временно игнорировать 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)
person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 16.09.2018

Мне часто хотелось, чтобы в командной строке не было опции для подавления этих обработчиков сигналов.

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 запустится.

person Waxrat    schedule 07.09.2019

Как вы можете видеть в опубликованном вами выводе, последнее исключение возникает на этапе деструктора: поэтому у вас есть 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()
person Michele d'Amico    schedule 04.11.2014
comment
Это не решение. Он хочет иметь возможность использовать его с flush=True с print. - person phantom; 04.11.2014
comment
Когда я пишу, я тестировал его на python 3.2... теперь я адаптирую его к python 3.4. - person Michele d'Amico; 04.11.2014
comment
@phantom Это версия для flush=True в print ... Это решение, не так ли? - person Michele d'Amico; 04.11.2014
comment
Это также работает. Большое спасибо! Я выбрал ответ от @serge-ballesta, потому что он ответил немного быстрее и избегал определения новой функции. Спасибо вам обоим. Мне нужно многое узнать об исключениях, о которых я никогда не садился и не читал. - person tommy.carstensen; 05.11.2014
comment
@tommy.carstensen спасибо за голос. Но мой ответ был первым stackoverflow.com/revisions/26736013/1. Он отредактировал его, потому что я написал его для python 3.2 (я был на работе), где ни flush=True, ни BrokenPipeError не было, но проблема все еще присутствовала, если вы попробуете с более низким head. Пока я пересматривал его, я добавил пример, чтобы объяснить, что на самом деле происходит, когда в деструкторе возникают исключения, чтобы лучше понять, как их перехватывать. Наконец, мой ответ более конкретен, потому что, отключив stderr, вы можете замаскировать больше проблем, чем связанные с сломанной трубой. - person Michele d'Amico; 05.11.2014
comment
@tommy.carstensen Во всяком случае, я понимаю ваш выбор, потому что ответ Sarge Balestra хорошо написан и дает простой способ найти решение. - person Michele d'Amico; 05.11.2014

В то время как другие очень подробно рассмотрели основную проблему, существует простой обходной путь:

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

Объяснение: tail буферизируется до тех пор, пока его STDIN не будет закрыт (python завершает работу и закрывает свой STDOUT). Таким образом, только хвост получает SIGPIPE, когда голова уходит. -n +1 фактически является неоперативным, заставляя tail выводить «хвост», начиная со строки 1, которая представляет собой весь буфер.

person notpeter    schedule 31.07.2019