python, как повторно вызвать исключение, которое уже отловлено? - PullRequest
0 голосов
/ 05 июля 2018
import sys
def worker(a):
    try:
        return 1 / a
    except ZeroDivisionError:
        return None


def master():
    res = worker(0)
    if not res:
        print(sys.exc_info())
        raise sys.exc_info()[0]

Как показано выше, у меня есть несколько функций, таких как работник. У них уже есть свой собственный блок try-исключения для обработки исключений. И тогда одна главная функция будет вызывать каждого работника. Прямо сейчас sys.exc_info () возвращает все None к 3 элементам, как повторно вызвать исключения в главной функции? Я использую Python 2.7

Одно обновление: У меня более 1000 работников, и у некоторых работников очень сложная логика, они могут обрабатывать несколько типов исключений одновременно. Поэтому мой вопрос: могу ли я просто поднять эти исключения из мастера, а не редактировать работы?

Ответы [ 3 ]

0 голосов
/ 05 июля 2018

То, что вы пытаетесь сделать, не сработает. После обработки исключения (без повторного его вызова) исключение и сопровождающее состояние очищаются, поэтому нет доступа к нему. Если вы хотите, чтобы исключение оставалось в живых, вы должны либо не обрабатывать его, либо поддерживать его вручную.

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

0 голосов
/ 05 июля 2018

В вашем случае исключение в worker возвращает None. Как только это произойдет, исключение не вернется. Если ваша основная функция знает, какими должны быть возвращаемые значения для каждой функции (например, ZeroDivisionError в worker reutrns None, вы можете вручную вызвать исключение.

Если вы не можете редактировать рабочие функции сами, я не думаю, что вы можете сделать слишком много. Возможно, вы сможете использовать некоторые решения из этого ответа , если они работают как в коде, так и на консоли.

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

Если вы хотите редактировать рабочие функции, то эскалирование исключения из кода, который вызвал функцию, на самом деле очень просто:

try: 
    # some code
except:
    # some response
    raise

Если вы используете пустой raise в конце блока catch, он вызовет то же исключение, которое только что перехватил. В качестве альтернативы вы можете назвать исключение, если вам нужно отладить печать и сделать то же самое, или даже вызвать другое исключение.

except Exception as e:
    # some code
    raise e
0 голосов
/ 05 июля 2018

Не проверено, но я подозреваю, что вы могли бы сделать что-то подобное. В зависимости от области видимости переменной вам придется ее изменить, но я думаю, вы поймете идею

try:
    something
except Exception as e:
    variable_to_make_exception = e

..... позже используйте переменную

пример использования этого способа обработки ошибок:

errors = {}
try:
    print(foo)
except Exception as e:
    errors['foo'] = e
try:
    print(bar)
except Exception as e:
    errors['bar'] = e


print(errors)
raise errors['foo']

выход ..

{'foo': NameError("name 'foo' is not defined",), 'bar': NameError("name 'bar' is not defined",)}
Traceback (most recent call last):
  File "<input>", line 13, in <module>
  File "<input>", line 3, in <module>
NameError: name 'foo' is not defined
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...