Ответ 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-namedclose_the_file
функция:ret = close_the_file(f);
Для стандартного файлового объекта
close_the_file(f)
делегирует функции Cfclose
, которая устанавливает условие ошибки, если есть еще данные для записи в указатель файла.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 Эта конкретная часть поведения является единственной частью, которая не имеет смысла, если вы не знаете об механизме кэширования атрибутов, упомянутом в предыдущих сносках.