Подтвердить что ты не робот

Как отключить "sys.excepthook отсутствует" ошибка?

NB: я не пытался воспроизвести проблему, описанную ниже под Windows, или с версиями Python, отличными от 2.7.3.

Самый надежный способ вызвать эту проблему - пропустить вывод следующего теста script через : (под bash):

try:
    for n in range(20):
        print n
except:
    pass

то есть:.

% python testscript.py | :
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

Мой вопрос:

Как я могу изменить тест script выше, чтобы избежать сообщения об ошибке при запуске script (показано в Unix/bash)?

(Как показывает тест script, ошибка не может быть захвачена с помощью try-except.)

Приведенный выше пример, по общему признанию, очень искусственный, но иногда я сталкиваюсь с той же проблемой, когда вывод script моего канала передается через какое-то стороннее программное обеспечение.

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

EDIT: Следующий script, который отличается от исходного выше, только тем, что он переопределяет sys.excepthook, ведет себя точно так же, как указано выше.

import sys
STDERR = sys.stderr
def excepthook(*args):
    print >> STDERR, 'caught'
    print >> STDERR, args

sys.excepthook = excepthook

try:
    for n in range(20):
        print n
except:
    pass
4b9b3361

Ответ 1

Как я могу изменить тест script выше, чтобы избежать сообщения об ошибке при запуске script (показано в Unix/bash)?

Вам нужно будет запретить script записывать что-либо на стандартный вывод. Это означает удаление любых операторов print и любое использование sys.stdout.write, а также любой код, который вызывает их.

Причина, по которой это происходит, заключается в том, что вы передаете ненулевой объем вывода из вашего Python script в то, что никогда не читается со стандартного ввода. Это не уникально для команды :; вы можете получить тот же результат по трубопроводу любой команде, которая не читает стандартный ввод, например

python testscript.py | cd .

Или для более простого примера рассмотрим script printer.py, содержащий не более

print 'abcde'

Тогда

python printer.py | python printer.py

приведет к той же ошибке.

Когда вы передаете вывод одной программы в другую, вывод, созданный программой записи, подкрепляется в буфере и ожидает, что программа чтения запросит данные из буфера. До тех пор, пока буфер не пуст, любая попытка закрыть файл записываемого файла должна завершиться ошибкой. Это основная причина сообщений, которые вы видите.

Конкретный код, вызывающий ошибку, находится в реализации языка C на языке Python, что объясняет, почему вы не можете поймать его блоком try/except: он запускается после того, как содержимое вашего script готовая обработка. В принципе, в то время как Python закрывается, он пытается закрыть stdout, но это терпит неудачу, потому что есть еще буферизованный вывод, ожидающий чтения. Таким образом, Python пытается сообщить об этой ошибке, как обычно, но sys.excepthook уже был удален как часть процедуры завершения, так что это не сработает. Затем Python пытается напечатать сообщение на sys.stderr, но это уже было освобождено так же, что это не удается. Причина, по которой вы видите сообщения на экране, заключается в том, что код Python содержит условное выражение fprintf для непосредственного вывода некоторого вывода в указатель файла, даже если выходной объект Python не существует.

Технические данные

Для тех, кто интересуется деталями этой процедуры, рассмотрим последовательность выключения интерпретатора Python, которая реализована в функции Py_Finalize of pythonrun.c.

  • После вызова выходных крючков и завершения потоков, код завершения вызывает PyImport_Cleanup, чтобы завершить и освободить все импортированные модули. Следующей задачей, выполняемой этой функцией, является удаление модуля sys, который в основном состоит из вызова _PyModule_Clear, чтобы очистить все записи в словаре модулей, включая, в частности, стандартные объекты потока (объекты Python), такие как stdout и stderr.
  • Когда значение удаляется из словаря или заменяется новым значением, его счетчик ссылок уменьшается, используя макрос Py_DECREF. Объекты, число отсчетов которых достигает нуля, становятся доступными для освобождения. Поскольку модуль sys содержит последние оставшиеся ссылки на стандартные объекты потока, когда эти ссылки не заданы _PyModule_Clear, они затем готовы к освобождению. 1
  • Освобождение файлового объекта Python выполняется функцией file_dealloc в fileobject.c. Этот первый вызывает метод close объекта файла Python, используя aptly-named close_the_file функция:

    ret = close_the_file(f);
    

    Для стандартного файлового объекта close_the_file(f) делегирует функции C fclose, которая устанавливает условие ошибки, если есть еще данные для записи в указатель файла. file_dealloc затем проверяет это условие ошибки и печатает первое сообщение, которое вы видите:

    if (!ret) {
        PySys_WriteStderr("close failed in file object destructor:\n");
        PyErr_Print();
    }
    else {
        Py_DECREF(ret);
    }
    
  • После печати этого сообщения Python затем пытается отобразить исключение, используя PyErr_Print. Это делегирует PyErr_PrintEx, и как часть его функций PyErr_PrintEx пытается получить доступ к принтеру исключений Python из sys.excepthook.

    hook = PySys_GetObject("excepthook");
    

    Это было бы хорошо, если бы это было сделано в обычном ходе программы Python, но в этой ситуации sys.excepthook уже очищен. 2 Python проверяет это условие ошибки и печатает второе сообщение как уведомление.

    if (hook && hook != Py_None) {
        ...
    } else {
        PySys_WriteStderr("sys.excepthook is missing\n");
        PyErr_Display(exception, v, tb);
    }
    
  • После уведомления нас о недостающем excepthook, Python затем возвращается к печати информации об исключении, используя PyErr_Display, который это метод по умолчанию для отображения трассировки стека. Самое первое, что делает эта функция, - это попытаться получить доступ к sys.stderr.

    PyObject *f = PySys_GetObject("stderr");
    

    В этом случае это не работает, потому что sys.stderr уже очищен и недоступен. 3 Таким образом, код вызывает fprintf напрямую, чтобы отправить третье сообщение на стандартную ошибку C поток.

    if (f == NULL || f == Py_None)
        fprintf(stderr, "lost sys.stderr\n");
    

Интересно, что поведение немного отличается в Python 3.4+, потому что процедура завершения теперь явно удаляет стандартные потоки вывода и ошибок перед встроенными модулями очищаются. Таким образом, если у вас есть данные, ожидающие записи, вы получите сообщение об ошибке, которое явно сигнализирует об этом условии, а не о "случайном" сбое в обычной процедуре завершения. Кроме того, если вы запустите

python printer.py | python printer.py

используя Python 3.4 (после размещения скобок в инструкции print, конечно), вы не получите никакой ошибки вообще. Я полагаю, что второй вызов Python по какой-то причине может потреблять стандартный ввод, но это целая отдельная проблема.


1 Собственно, это ложь. Механизм импорта Python кэширует копию каждого импортированного словаря модуля, который не выводится до _PyImport_Fini работает позже в реализации Py_Finalize, и именно тогда исчезают последние ссылки на стандартные потоковые объекты, Когда счетчик ссылок достигнет нуля, Py_DECREF немедленно освободит объекты. Но все, что имеет значение для основного ответа, состоит в том, что ссылки удаляются из словаря модуля sys и затем освобождаются через некоторое время.

2 Опять же, это потому, что словарь модуля sys полностью очищен, прежде чем что-то действительно освобождено, благодаря механизму кэширования атрибутов. Вы можете запустить Python с параметром -vv, чтобы увидеть, что все атрибуты модуля не установлены, прежде чем вы получите сообщение об ошибке закрытия указателя файла.

3 Эта конкретная часть поведения является единственной частью, которая не имеет смысла, если вы не знаете об механизме кэширования атрибутов, упомянутом в предыдущих сносках.

Ответ 2

Сегодня я столкнулся с такой проблемой и пошел искать ответ. Я думаю, что простой способ решения проблемы заключается в том, чтобы вы сначала запустили stdio, поэтому блоки python вместо отказа во время script shutdown. Например:

--- a/testscript.py
+++ b/testscript.py
@@ -9,5 +9,6 @@ sys.excepthook = excepthook
 try:
     for n in range(20):
         print n
+    sys.stdout.flush()
 except:
     pass

Затем с этим script ничего не происходит, поскольку исключение (IOError: [Errno 32] Broken pipe) подавляется с помощью try... except.

$ python testscript.py  | :
$

Ответ 3

В вашей программе выбрасывается исключение, которое невозможно поймать с помощью блока try/except. Чтобы поймать его, переопределите функцию sys.excepthook:

import sys
sys.excepthook = lambda *args: None

Из документация:

sys.excepthook(тип, значение, трассировка)

Когда исключение возбуждается и не получается, вызов интерпретатора sys.excepthook с тремя аргументами, класс исключения, исключение экземпляр и объект трассировки. В интерактивном сеансе это происходит непосредственно перед возвратом элемента управления в приглашение; в Python это произойдет непосредственно перед выходом программы. Обработка такие исключения верхнего уровня могут быть настроены путем назначения другого функция с тремя аргументами для sys.excepthook.

Иллюстративный пример:

import sys
import logging

def log_uncaught_exceptions(exception_type, exception, tb):

    logging.critical(''.join(traceback.format_tb(tb)))
    logging.critical('{0}: {1}'.format(exception_type, exception))

sys.excepthook = log_uncaught_exceptions

Ответ 4

Я понимаю, что это старый вопрос, но я нашел его в поиске Google ошибки. В моем случае это была ошибка кодирования. Одним из моих последних утверждений было:

print "Good Bye"

Решение просто фиксировало синтаксис:

print ("Good Bye")

[Raspberry Pi Zero, Python 2.7.9]