Обработка исключений на нескольких уровнях вызова - PullRequest
0 голосов
/ 27 февраля 2019

Как вы лучше всего справляетесь с несколькими уровнями методов в иерархии вызовов, которые вызывают исключения, так что, если это фатальная ошибка, программа завершит работу (после отображения диалога об ошибке)?

Я в основномисходя из Явы.Там я бы просто объявил любые методы как throws Exception, перебросил бы их и поймал где-то на верхнем уровне.

Однако Python отличается.Мой код Python в основном выглядит следующим образом.

РЕДАКТИРОВАТЬ: добавил гораздо более простой код ...

Основная функция ввода (plugin.py):

def main(catalog):

    print "Executing main(catalog)... "
    # instantiate generator
    gen = JpaAnnotatedClassGenerator(options)

    # run generator
    try:
        gen.generate_bar()  # doesn't bubble up
    except ValueError as error:
        Utilities.show_error("Error", error.message, "OK", "", "")
        return

    ... usually do the real work here if no error

JpaAnnotatedClassGenerator class (engine.py):

class JpaAnnotatedClassGenerator:

    def generate_bar(self):
        self.generate_value_error()

    def generate_value_error(self):
        raise ValueError("generate_value_error() raised an error!")

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

ВОПРОС : Как это лучше всего сделать в Python?Действительно ли мне нужно повторять try-except для каждого вызываемого метода?

Кстати: я использую Python 2.6.x и не могу выполнить обновление из-за привязки к MySQL Workbench, который предоставляет интерпретатор (Python 3 включених список обновлений).

Ответы [ 2 ]

0 голосов
/ 27 февраля 2019

Хорошо, если ошибки всплывают

Исключения Python не проверяются, что означает, что вы не обязаны их объявлять или обрабатывать.Даже если вы знаете, что что-то может возникнуть, поймайте ошибку, только если вы собираетесь что-то с этим сделать.Хорошо иметь прозрачные для исключения слои, которые грациозно прерывают пузырьки сквозь них:

def logged_get(map: dict, key: str):
    result = map[key]  # this may raise, but there is no state to corrupt
    # the following is not meaningful if an exception occurred
    # it is fine for it to be skipped by the exception bubbling up
    print(map, '[%s]' % key, '=>', result)
    return result

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

Итак, просто позвоните self.create_collection_embeddable_class_stub так, как вы это делаете.

Это нормально для ошибок, чтобы убить приложение

Даже если ничего не обрабатывает ошибка, интерпретатор делает.Вы получаете трассировку стека, показывая, что пошло не так и где.Фатальные ошибки типа «случается, только если есть ошибка» могут «безопасно» всплывать, чтобы показать, что пошло не так.

На самом деле, выход из интерпретатора и утверждений также использует этот механизм.

>>> assert 2 < 1, "This should never happen"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AssertionError: This should never happen

Для многих служб вы можете использовать это даже при развертывании - например, systemd будет регистрировать это для системной службы Linux.Попытайтесь подавлять ошибки извне только в том случае, если безопасность вызывает беспокойство или пользователи не могут обработать ошибку.

Можно использовать точные ошибки

Поскольку исключения не проверяются, вы можете использовать произвольныемногие без перенапрягают ваш API.Это позволяет использовать пользовательские ошибки, которые сигнализируют о различном уровне проблем:

class DBProblem(Exception):
    """Something is wrong about our DB..."""

class DBEntryInconsistent(DBProblem):
    """A single entry is broken"""

class DBInconsistent(DBProblem):
    """The entire DB is foobar!"""

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

try:
    gen.generate_classes(catalog)
except DBEntryInconsistent:
    logger.error("aborting due to corrupted entry")
    sys.exit(1)
except DBInconsistent as err:
    logger.error("aborting due to corrupted DB")
    Utility.inform_db_support(err)
    sys.exit(1)
# do not handle ValueError, KeyError, MemoryError, ...
# they will show up as a stack trace
0 голосов
/ 27 февраля 2019

Если вы не поймаете исключение, оно будет пузыриться в стеке вызовов, пока кто-нибудь не поймет.Если никто не ловит его, среда выполнения получит его и умрет с сообщением об ошибке исключения и полным обратным отслеживанием.Итак, вам не нужно явно ловить и пересматривать ваше исключение везде, что фактически лишило бы смысла иметь исключения.На самом деле, несмотря на то, что он в основном используется для ошибок / непредвиденных ситуаций, исключения являются, прежде всего, инструментом потока управления, позволяющим выйти из нормального потока выполнения и передать управление (и некоторую информацию) в любое произвольное место в стеке вызовов.

Из этого POV ваш код кажется наиболее правильным (предостережение: я не удосужился прочитать все это, просто быстро посмотрел), за исключением (без отступов) для пары очков:

ПервыйВы должны определить свой собственный особый класс (ы) исключений вместо использования встроенного ValueError (вы можете наследовать от него, если это имеет смысл для вас), так что вы уверены, что поймаете только те исключения, которые ожидаете (довольно много слоев)в разделе «ваш собственный код может вызвать ошибку ValueError, которую вы не ожидали).

Затем вы можете (или нет, в зависимости от того, как используется ваш код) также захотеть добавить универсальный верхний уровеньобработчик в вашей функции main(), чтобы вы могли правильно регистрировать (используя модуль logger) все ошибки и, в конечном итоге, свободные ресурсы, делатьнекоторая очистка и т. д. до того, как ваш процесс умрет.

В качестве примечания, вы также можете изучить и использовать правильное форматирование строки, и - если, по крайней мере, проблема с perfs - избегать повторяющихся постоянных вызовов, подобных этой:

elif AnnotationUtil.is_embeddable_table(table) and AnnotationUtil.is_secondary_table(table):
    # ...
elif AnnotationUtil.is_embeddable_table(table):
    # ...
elif AnnotationUtil.is_secondary_table(table):
    # ...

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

EDIT:

При попытке уловить ошибку в функции main () исключения НЕ всплывают, но когда я использую этот шаблон на один уровень глубже, кажется, что всплывает всплывающее окно.

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

def deeply_nested():
    raise ValueError("foo")

def nested():
    return deeply_nested()

def firstline():
    return nested()

def main():
    try:
        firstline()
    except ValueError as e:
        print("got {}".format(e))
    else:
        print("you will not see me")

if __name__ == "__main__":
    main()

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

Ух ... Даже встроенный механизм ожидания должен работать, как и ожидалось - по крайней мере, для той части стека вызовов, которая зависит от вашего main функция (не может сказать, что происходит выше в стеке вызовов).Но учитывая то, как MySQL обрабатывает ошибки (как насчет того, чтобы ваши данные молча усекались?), Я не был бы особенно удивлен, если бы они взломали среду выполнения, чтобы молча передать любую ошибку в коде плагинов xD

...