Если вы довольно часто используете wxPython, со временем вы поймёте, что некоторые исключения довольно сложно обработать. И причина подобной сложности кроется в том, что wxPython использует для C++ пакетов враппер, который называется wxWidgets. Таким образом ваше приложение превращается в микс C++ и Python. Это значит, что события буквально бьют ключом из C++ в Python и обратно. В зависимости от того, где появляется исключение (со стороны Python или со стороны C++), определяется возможный шанс его обнаружения и обработки.
Обработка исключений
Одним из решений, которое работает в большинстве случаев, является использование sys.excepthook в Python. Мы потратим немного времени на то, чтобы покопаться в этом коде и понять, как вы можете использовать его функциональность.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import sys import traceback import wx import wx.lib.agw.genericmessagedialog as GMD class Panel(wx.Panel): """""" def __init__(self, parent): """Constructor""" wx.Panel.__init__(self, parent) btn = wx.Button(self, label="Raise Exception") btn.Bind(wx.EVT_BUTTON, self.onExcept) def onExcept(self, event): """ Вызвать ошибку """ 1/0 class Frame(wx.Frame): """""" def __init__(self): """Constructor""" wx.Frame.__init__(self, None, title="Exceptions") sys.excepthook = MyExceptionHook panel = Panel(self) self.Show() class ExceptionDialog(GMD.GenericMessageDialog): """ Диалог, отображающий ошибку """ def __init__(self, msg): """Constructor""" GMD.GenericMessageDialog.__init__(self, None, msg, "Exception!", wx.OK|wx.ICON_ERROR) def MyExceptionHook(etype, value, trace): """ Хендлер для всех необработанных исключений. :param `etype`: the exception type (`SyntaxError`, `ZeroDivisionError`, etc...); :type `etype`: `Exception` :param string `value`: the exception error message; :param string `trace`: the traceback header, if any (otherwise, it prints the standard Python header: ``Traceback (most recent call last)``. """ frame = wx.GetApp().GetTopWindow() tmp = traceback.format_exception(etype, value, trace) exception = "".join(tmp) dlg = ExceptionDialog(exception) dlg.ShowModal() dlg.Destroy() if __name__ == "__main__": app = wx.App(False) frame = Frame() app.MainLoop() |
В данном примере мы создаём панель с кнопкой, которая специально вызовет исключение. Мы обрабатываем исключение посредством переадресации sys.excepthook в нашу функцию MyExceptionHook. Эта функция отформатирует след, оставленный исключением, затем отформатирует само исключение, чтобы сделать его читабельным и затем отобразит диалог с информацией о данном исключении.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Создаём декоратор, обнаруживающий исключения
Robin Dunn, создатель wxPython, считает, что было бы круто, если бы кто-то предоставил ему декоратор, который сможет обрабатывать исключения, который можно будет добавить в качестве примера на вики-страницу wxPython. Моя первая идея относительно декоратора выглядела следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import logging import wx class ExceptionLogging(object): def __init__(self, fn): self.fn = fn # создаём logging инстанцию self.log = logging.getLogger("wxErrors") self.log.setLevel(logging.INFO) # создаём logging файловый хендлер/форматер log_fh = logging.FileHandler("error.log") formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s") log_fh.setFormatter(formatter) self.log.addHandler(log_fh) def __call__(self, evt): try: self.fn(self, evt) except Exception as e: self.log.exception("Exception") class Panel(wx.Panel): """""" def __init__(self, parent): """Constructor""" wx.Panel.__init__(self, parent) btn = wx.Button(self, label="Raise Exception") btn.Bind(wx.EVT_BUTTON, self.onExcept) @ExceptionLogging def onExcept(self, event): """ Raise an error """ 1/0 class Frame(wx.Frame): """""" def __init__(self): """Constructor""" wx.Frame.__init__(self, None, title="Exceptions") panel = Panel(self) self.Show() if __name__ == "__main__": app = wx.App(False) frame = Frame() app.MainLoop() |
В этом коде мы создаём класс, который создаёт logging-инстанцию. Затем мы отменяем метод __call__, чтобы получить возможность вызова метода в хендлере исключений, чтобы обнаружить сами исключения. Проще говоря, здесь мы создаём классовый декоратор. Затем мы декорируем хендлер событий с помощью нашего logging-класса исключений. Это не совсем то, чего хотел мистер Данн, так как декоратору нужно иметь возможность врэппить и другие функции. Так что я немного его отредактировал, ведь мне в голову пришла мысль незначительной корректировки, которую вы можете заметить ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
import logging import wx class ExceptionLogging(object): def __init__(self, fn, *args, **kwargs): self.fn = fn # создаём logging инстанцию self.log = logging.getLogger("wxErrors") self.log.setLevel(logging.INFO) # создаём logging файловый хендлер/форматер log_fh = logging.FileHandler("error.log") formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s") log_fh.setFormatter(formatter) self.log.addHandler(log_fh) def __call__(self, *args, **kwargs): try: self.fn(self, *args, **kwargs) except Exception as e: self.log.exception("Exception") class Panel(wx.Panel): """""" def __init__(self, parent): """Constructor""" wx.Panel.__init__(self, parent) btn = wx.Button(self, label="Raise Exception") btn.Bind(wx.EVT_BUTTON, self.onExcept) @ExceptionLogging def onExcept(self, event): """ Raise an error """ 1/0 class Frame(wx.Frame): """""" def __init__(self): """Constructor""" wx.Frame.__init__(self, None, title="Exceptions") panel = Panel(self) self.Show() if __name__ == "__main__": app = wx.App(False) frame = Frame() app.MainLoop() |
В этот раз метод __call__ может принять любое количество аргументов или ключевых слов-аргументов, что делает его более гибким. Это по-прежнему не то, что хотел видеть мистер Данн, так что он написал следующий пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
from __future__ import print_function import logging import wx print(wx.version()) def exceptionLogger(func, loggerName=''): """ Простой декоратор, который обнаружит и внесёт в лог любые исключения, которые могут появится в корневом логгере. """ assert callable(func) mylogger = logging.getLogger(loggerName) # врапаем новую функцию вокруг вызванной def logger_func(*args, **kw): try: if not kw: return func(*args) return func(*args, **kw) except Exception: mylogger.exception('Exception in %s:', func.__name__) logger_func.__name__ = func.__name__ logger_func.__doc__ = func.__doc__ if hasattr(func, '__dict__'): logger_func.__dict__.update(func.__dict__) return logger_func def exceptionLog2Logger(loggerName): """ декоратор, который обнаружит и внесёт в лог любые исключения, которые могут появится в именном (названом) логгере """ import functools return functools.partial(exceptionLogger, loggerName=loggerName) class Panel(wx.Panel): """""" def __init__(self, parent): """Constructor""" wx.Panel.__init__(self, parent) btn = wx.Button(self, label="Raise Exception") btn.Bind(wx.EVT_BUTTON, self.onExcept) @exceptionLog2Logger('testLogger') def onExcept(self, event): """ Raise an error """ print(self, event) print(isinstance(self, wx.Panel)) # trigger an exception 1/0 class Frame(wx.Frame): """""" def __init__(self): """Constructor""" wx.Frame.__init__(self, None, title="Exceptions") panel = Panel(self) self.Show() if __name__ == "__main__": # Устанавливаем логгер по умолчанию log = logging.getLogger('testLogger') log.setLevel(logging.INFO) # создаём logging-файловый хендлер/форматер log_fh = logging.FileHandler("error.log") formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s") log_fh.setFormatter(formatter) log.addHandler(log_fh) app = wx.App(False) frame = Frame() app.MainLoop() |
Здесь отображены несколько примеров декоратора. Этот пример демонстрирует более традиционную методологию создания декораторов. В нём также есть немного мета-программирования. Первый пример проверяет то, что на самом деле произошло с тем, что было вызвано. Затем он создаёт логгер и обворачивает вызванное хендлером исключение. До того, как он вернётся к врапнутой функции, эта функция изменится таким образом, что будете иметь то же название и строку данных, что и изначальна функция, которую она оборачивала.
Я надеюсь, что вы сможете отбросить это и использовать functools.wraps вместо предложенного варианта, но впредь я постараюсь выражать свои мысли яснее. А для тех, кто не понял сути, лучше обратить к оригинальным туториалам, там всё изложено немного проще.
Примечание: Если вы запускаете последний пример кода в Python 3, то можете удалить импорт формы __future__*, так как он больше не требуется. Но, если вы вдруг забудете это сделать, ничего страшного не произойдёт.
Итоги
Теперь вы знаете несколько способов обнаружения исключений. Надеюсь, что в создании вашего приложения вам это пригодится. Веселитесь!
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»