ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов
Скачать 7.92 Mb.
|
IndentationError — неправильно расставлены отступы в программе; IndexError — указанный индекс не существует в последовательности; KeyError — указанный ключ не существует в словаре; KeyboardInterrupt — нажата комбинация клавиш MemoryError — интерпретатору существенно не хватает оперативной памяти; Глава 14. Обработка исключений 273 NameError — попытка обращения к идентификатору до его определения; NotImplementedError — должно возбуждаться в абстрактных методах; OSError — базовый класс для всех исключений, возбуждаемых в ответ на возникновение ошибок в операционной системе (отсутствие запрошенного файла, недостаток места на диске и пр.); OverflowError — число, получившееся в результате выполнения арифметической опера- ции, слишком велико, чтобы Python смог его обработать; RecursionError — превышено максимальное количество проходов рекурсии; RuntimeError — неклассифицированная ошибка времени выполнения; StopIteration — возбуждается методом __next__() как сигнал об окончании итераций; SyntaxError — синтаксическая ошибка; SystemError — ошибка в самой программе интерпретатора Python; TabError — в исходном коде программы встретился символ табуляции, использование которого для создания отступов недопустимо; TypeError — тип объекта не соответствует ожидаемому; UnboundLocalError — внутри функции переменной присваивается значение после обра- щения к одноименной глобальной переменной; UnicodeDecodeError — ошибка преобразования последовательности байтов в строку; UnicodeEncodeError — ошибка преобразования строки в последовательность байтов; UnicodeTranslationError — ошибка преобразования строки в другую кодировку; ValueError — переданный параметр не соответствует ожидаемому значению; ZeroDivisionError — попытка деления на ноль. 14.4. Пользовательские исключения Для возбуждения пользовательских исключений предназначены две инструкции: raise и assert Инструкция raise возбуждает заданное исключение. Она имеет несколько вариантов фор- мата: raise <Экземпляр класса> raise <Название класса> raise <Экземпляр или название класса> from <Объект исключения> raise В первом варианте формата инструкции raise указывается экземпляр класса возбуждае- мого исключения. При создании экземпляра можно передать конструктору класса данные, которые станут доступны через второй параметр в инструкции except . Приведем пример возбуждения встроенного исключения ValueError : >>> raise ValueError("Описание исключения") Traceback (most recent call last): File " ", line 1, in ValueError: Описание исключения 274 Часть I. Основы языка Python Пример обработки этого исключения показан в листинге 14.10. Листинг 14.10. Программное возбуждение исключения try: raise ValueError("Описание исключения") except ValueError as msg: print(msg) # Выведет: Описание исключения В качестве исключения можно указать экземпляр пользовательского класса (листинг 14.11). Листинг 14.11. Создание собственного исключения class MyError(Exception): def __init__(self, value): self.msg = value def __str__(self): return self.msg # Обработка пользовательского исключения try: raise MyError("Описание исключения") except MyError as err: print(err) # Вызывается метод __str__() print(err.msg) # Обращение к атрибуту класса # Повторно возбуждаем исключение raise MyError("Описание исключения") Результат выполнения: Описание исключения Описание исключения Traceback (most recent call last): File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений II/Примеры/14/14.11.py", line 13, in MyError: Описание исключения Класс Exception поддерживает все необходимые методы для вывода сообщения об ошибке. Поэтому в большинстве случаев достаточно создать пустой класс, который наследует класс Exception (листинг 14.12). Листинг 14.12. Упрощенный способ создания собственного исключения class MyError(Exception): pass try: raise MyError("Описание исключения") except MyError as err: print(err) # Выведет: Описание исключения Во втором варианте формата инструкции raise в первом параметре задается объект клас- са, а не экземпляр: Глава 14. Обработка исключений 275 try: raise ValueError # Эквивалентно: raise ValueError() except ValueError: print("Сообщение об ошибке") В третьем варианте формата инструкции raise в первом параметре задается экземпляр класса или просто название класса, а во втором параметре указывается объект исключения. В этом случае объект исключения сохраняется в атрибуте __cause__ . При обработке вло- женных исключений эти данные используются для вывода информации не только о послед- нем исключении, но и о первоначальном исключении. Пример этого варианта формата ин- струкции raise можно увидеть в листинге 14.13. Листинг 14.13. Применение третьего варианта формата инструкции raise try: x = 1 / 0 except Exception as err: raise ValueError() from err Результат выполнения: Traceback (most recent call last): File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений II/Примеры/14/14.13.py", line 2, in ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений II/Примеры/14/14.13.py", line 4, in ValueError Как видно из результата, мы получили информацию не только по исключению ValueError , но и по исключению ZeroDivisionError . Следует заметить, что при отсутствии инструкции from информация сохраняется неявным образом. Если убрать инструкцию from в предыду- щем примере, мы получим следующий результат: Traceback (most recent call last): File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений II/Примеры/14/14.13.py", line 2, in ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений II/Примеры/14/14.13.py", line 4, in ValueError 276 Часть I. Основы языка Python Четвертый вариант формата инструкции raise позволяет повторно возбудить последнее исключение и обычно применяется в коде, следующем за инструкцией except . Пример это- го варианта показан в листинге 14.14. Листинг 14.14. Применение четвертого варианта формата инструкции raise class MyError(Exception): pass try: raise MyError("Сообщение об ошибке") except MyError as err: print(err) raise # Повторно возбуждаем исключение Результат выполнения: Сообщение об ошибке Traceback (most recent call last): File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений II/Примеры/14/14.14.py", line 3, in MyError: Сообщение об ошибке Инструкция assert возбуждает исключение AssertionError , если логическое выражение возвращает значение False . Инструкция имеет следующий формат: assert <Логическое выражение>[, <Данные>] Инструкция assert эквивалентна следующему коду: if __debug__: if not <Логическое выражение>: raise AssertionError(<Данные>) Если при запуске программы используется флаг -O , то переменная __debug__ будет иметь ложное значение. Таким образом можно удалить все инструкции assert из байт-кода. Пример использования инструкции assert представлен в листинге 14.15. Листинг 14.15. Использование инструкции assert try: x = -3 assert x >= 0, "Сообщение об ошибке" except AssertionError as err: print(err) # Выведет: Сообщение об ошибке ГЛ А В А 15 Итераторы, контейнеры и перечисления Язык Python поддерживает средства для создания классов особого назначения: итераторов, контейнеров и перечислений. Итераторы — это классы, генерирующие последовательности каких-либо значений. Такие классы мы можем задействовать, например, в циклах for : class MyIterator: # Определяем класс-итератор it = MyIterator() # Создаем его экземпляр for v in it: # и используем в цикле for Контейнеры — классы, которые могут выступать как последовательности (списки или кор- тежи) или отображения (словари). Мы можем обратиться к любому элементу экземпляра такого класса через его индекс или ключ: class MyList: # Определяем класс-список class MyDict: # Определяем класс-словарь lst, dct = MyList(), MyDict() # Используем их lst[0] = 1 dct["first"] = 578 print(lst[1]), print(dct["second"]) Перечисления — особые классы, представляющие наборы каких-либо именованных вели- чин. В этом смысле они аналогичны подобным типам данных, доступным в других языках программирования, — например, в C: from enum import Enum # Импортируем базовый класс Enum class Versions(Enum): # Определяем класс-перечисление Python2.7 = "2.7" Python3.6 = "3.6" # Используем его if python_version == Versions.Python3.6: 278 Часть I. Основы языка Python 15.1. Итераторы Для того чтобы превратить класс в итератор, нам следует переопределить в нем два специ- альных метода: __iter__(self) — говорит о том, что этот класс является итератором (поддерживает итерационный протокол, как говорят Python-программисты). Он должен возвращать сам экземпляр этого класса, а также при необходимости может выполнять всевозможные предустановки. Если в классе одновременно определены методы __iter__() и __getitem__() (о нем будет рассказано позже), предпочтение отдается первому методу; __next__(self) — вызывается при выполнении каждой итерации и должен возвращать очередное значение из последовательности. Если последовательность закончилась, в этом методе следует возбудить исключение StopIteration , которое сообщит вызы- вающему коду об окончании итераций. Для примера рассмотрим класс, хранящий строку и на каждой итерации возвращающий очередной ее символ, начиная с конца (листинг 15.1). Листинг 15.1. Класс-итератор class ReverseString: def __init__(self, s): self.__s = s def __iter__(self): self.__i = 0 return self def __next__(self): if self.__i > len(self.__s) - 1: raise StopIteration else: a = self.__s[-self.__i - 1] self.__i = self.__i + 1 return a Проверим его в действии: >>> s = ReverseString("Python") >>> for a in s: print(a, end="") nohtyP Результат вполне ожидаем — строка, выведенная задом наперед. Также мы можем переопределить специальный метод __len()__ , который вернет количест- во элементов в последовательности, и, разумеется, специальные методы __str()__ и __repr()__ , возвращающие строковое представление итератора (все эти методы были рас- смотрены в главе 13). Перепишем код нашего класса-итератора, добавив в него определение методов __len()__ и __str()__ (листинг 15.2 — часть кода опущена). Глава 15. Итераторы, контейнеры и перечисления 279 Листинг 15.2. Расширенный класс-итератор class ReverseString: def __len__(self): return len(self.__s) def __str__(self): return self.__s[::-1] Теперь мы можем получить длину последовательности, хранящейся в экземпляре класса ReverseString , и его строковое представление: >>> s = ReverseString("Python") >>> print(len(s)) 6 >>> print(str(s)) nohtyP 15.2. Контейнеры Python позволяет создать как контейнеры-последовательности, аналогичные спискам и кор- тежам, так и контейнеры-отображения, т. е. словари. Сейчас мы узнаем, как это делается. 15.2.1. Контейнеры-последовательности Чтобы класс смог реализовать функциональность последовательности, нам следует переоп- ределить в нем следующие специальные методы: __getitem__(self, <Индекс>) — вызывается при извлечении элемента последовательно- сти по его индексу с помощью операции <Экземпляр класса>[<Индекс>] . Метод должен возвращать значение, расположенное по этому индексу. Если индекс не является целым числом или срезом, должно возбуждаться исключение TypeError , а если индекса как та- кового не существует, следует возбудить исключение IndexError ; __setitem__(self, <Индекс>, <Значение>) — вызывается в случае присваивания нового значения элементу последовательности с заданным индексом (операция <Экземпляр класса>[<Индекс>] = <Новое значение> ). Метод не должен возвращать результата. В случае задания индекса недопустимого типа и отсутствия такого индекса в последова- тельности следует возбуждать те же исключения, что и в случае метода __getitem__() ; __delitem__(self, <Ключ>) — вызывается в случае удаления элемента последовательно- сти с заданным индексом с помощью выражения del <Экземпляр класса>[<Ключ>] Метод не должен возвращать результата. В случае задания индекса недопустимого типа и отсутствия такого индекса в последовательности следует возбуждать те же исключе- ния, что и в случае метода __getitem__() ; __contains__(self, <Значение>) — вызывается при проверке существования заданного значения в последовательности с применением операторов in и not in . Метод должен возвращать True , если такое значение есть, и False — в противном случае. В классе-последовательности мы можем дополнительно реализовать функциональность итератора (см. разд. 15.1), переопределив специальные методы __iter__() , __next__() и __len__() . Чаще всего так и поступают. 280 Часть I. Основы языка Python Мы уже давно знаем, что строки в Python являются неизменяемыми. Давайте же напишем класс MutableString , представляющий строку, которую можно изменять теми же способа- ми, что и список (листинг 15.3). Листинг 15.3. Класс MutableString class MutableString: def __init__(self, s): self.__s = list(s) # Реализуем функциональность итератора def __iter__(self): self.__i = 0 return self def __next__(self): if self.__i > len(self.__s) - 1: raise StopIteration else: a = self.__s[self.__i] self.__i = self.__i + 1 return a def __len__(self): return len(self.__s) def __str__(self): return "".join(self.__s) # Определяем вспомогательный метод, который будет проверять # корректность индекса def __iscorrectindex(self, i): if type(i) == int or type(i) == slice: if type(i) == int and i > self.__len__() - 1: raise IndexError else: raise TypeError # Реализуем функциональность контейнера-списка def __getitem__(self, i): self.__iscorrectindex(i) return self.__s[i] def __setitem__(self, i, v): self.__iscorrectindex(i) self.__s[i] = v def __delitem__(self, i): self.__iscorrectindex(i) del self.__s[i] def __contains__(self, v): return v in self.__s Глава 15. Итераторы, контейнеры и перечисления 281 Проверим свеженаписанный класс в действии: >>> s = MutableString("Python") >>> print(s[-1]) n >>> s[0] = "J" >>> print(s) Jython >>> del s[2:4] >>> print(s) Juon Теперь проверим, как наш класс обрабатывает нештатные ситуации. Введем вот такой код, обращающийся к элементу с несуществующим индексом: >>> s[9] = "u" В ответ интерпретатор Python выдаст вполне ожидаемое сообщение об ошибке: Traceback (most recent call last): File " ", line 1, in File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений II/Примеры/15/15.3.py", line 36, in __setitem__ self.__iscorrectindex(i) File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений II/Примеры/15/15.3.py", line 27, in __iscorrectindex raise IndexError IndexError 15.2.2. Контейнеры-словари Класс, реализующий функциональность перечисления, должен переопределять уже знако- мые нам методы: __getitem__() , __setitem__() , __delitem__() и __contains__() . Разумеет- ся, при этом следует сделать поправку на то, что вместо индексов здесь будут использо- ваться ключи произвольного типа (как правило, строкового). Давайте исключительно для практики напишем класс Version , который будет хранить но- мер версии интерпретатора Python, разбитый на части: старшая цифра, младшая цифра и подрелиз, при этом доступ к частям номера версии будет осуществляться по строковым ключам, как в обычном словаре Python (листинг 15.4). Ради простоты чтения кода функ- циональность итератора реализовывать не станем, а также заблокируем операцию удаления элемента словаря, возбудив в методе __delitem__() исключение TypeError Листинг 15.4. Класс Version class Version: def __init__(self, major, minor, sub): self.__major = major # Старшая цифра self.__minor = minor # Младшая цифра self.__sub = sub # Подверсия def __str__(self): return str(self.__major) + "." + str(self.__minor) + "." + str(self.__sub) 282 Часть I. Основы языка Python # Реализуем функциональность словаря def __getitem__(self, k): if k == "major": return self.__major elif k == "minor": return self.__minor elif k == "sub": return self.__sub else: raise IndexError def __setitem__(self, k, v): if k == "major": self.__major = v elif k == "minor": self.__minor = v elif k == "sub": self.__sub = v else: raise IndexError def __delitem__(self, k): raise TypeError def __contains__(self, v): return v == "major" or v == "minor" or v == "sub" Чтобы наш новый класс не бездельничал, дадим ему работу, введя такой код: >>> v = Version(3, 6, 3) >>> print(v["major"]) 3 >>> v["sub"] = 4 >>> print(str(v)) 3.6.4 Как видим, все работает как надо. 15.3. Перечисления Перечисление — это определенный самим программистом набор каких-либо именованных значений. Обычно они применяются для того, чтобы дать понятные имена каким-либо зна- чениям, используемым в коде программы, — например, кодам ошибок, возвращаемых функциями Windows API. |