То, что вы пытаетесь сделать, не сработает. После обработки исключения (без повторного его вызова) исключение и сопровождающее состояние очищаются, поэтому нет доступа к нему. Если вы хотите, чтобы исключение оставалось в живых, вы должны либо не обрабатывать его, либо поддерживать его вручную.
Это не так просто найти в документах (основные детали реализации CPython немного проще, но в идеале мы хотим знать, что определяет язык Python), но он там, скрыт в except
ссылка:
… Это означает, что исключение должно быть присвоено другому имени, чтобы иметь возможность ссылаться на него после условия исключения. Исключения очищаются, потому что с прикрепленной к ним трассировкой они образуют ссылочный цикл со стеком, сохраняя все локальные объекты в этом кадре до следующей сборки мусора.
Перед выполнением комплекта предложения исключений сведения об исключении сохраняются в модуле sys
и доступны через sys.exc_info()
. sys.exc_info()
возвращает 3-кортеж, состоящий из класса исключения, экземпляра исключения и объекта трассировки (see section Иерархия стандартных типов), идентифицирующего точку в программе, где произошло исключение. sys.exc_info()
значения возвращаются к своим предыдущим значениям (до вызова) при возврате из функции, которая обработала исключение.
Кроме того, это действительно точка обработки исключений: когда функция обрабатывает исключение, для мира вне этой функции, похоже, что исключений не было. Это даже более важно в Python, чем во многих других языках, потому что Python так беспорядочно использует исключения - каждый цикл for
, каждый вызов hasattr
и т. Д. Вызывает и обрабатывает исключение, и вы не хотите их видеть .
Итак, самый простой способ сделать это - просто изменить рабочих так, чтобы они не обрабатывали исключения (или не регистрировали, а затем повторно вызывали их, или что-то еще), и позволяли обработке исключений работать так, как это было задумано.
Есть несколько случаев, когда вы не можете этого сделать. Например, если ваш реальный код выполняет рабочие в фоновых потоках, вызывающая сторона не увидит исключение. В этом случае вам необходимо передать его обратно вручную. Для простого примера давайте изменим API ваших рабочих функций, чтобы они возвращали значение и исключение:
def worker(a):
try:
return 1 / a, None
except ZeroDivisionError as e:
return None, e
def master():
res, e = worker(0)
if e:
print(e)
raise e
Очевидно, что вы можете расширить это, чтобы вернуть целую exc_info
тройку или что угодно еще; Я просто держу это как можно более простым для примера.
Если вы заглянете внутрь таких вещей, как concurrent.futures
, именно так они обрабатывают передачу исключений из задач, выполняющихся в потоке или пуле процессов, обратно к родительскому элементу (например, когда вы ожидаете Future
).
Если вы не можете модифицировать рабочих, вам в основном не повезло. Конечно, вы могли бы написать какой-нибудь ужасный код для исправления рабочих во время выполнения (используя inspect
для получения их исходного кода, а затем ast
для его анализа, преобразования и повторной компиляции или погружаясь прямо в байт-код) , но это почти никогда не будет хорошей идеей для любого вида производственного кода.