Лутц М. - Изучаем Python (1077325), страница 157
Текст из файла (страница 157)
Объекты исключений До сих пор я преднамеренно умалчивал о том, чем в действительности являются исключения. В языке Ру1Ьоп понятие исключения было обобщено — как уже упоминалось в предыдущей главе, они могут идентифицироваться строковыми объектами или объектами экземпляра класса. В настоящее время предпочтительнее использовать объекты экземпляров класса, но в скором будущем они станут обязательными.
У обоих подходов имеются свои достоинства, но классы обеспечивают лучшее решение, когда дело доходит до поддержки иерархий исключений. Проще говоря, исключения на основе классов позволяют создавать исключения, организованные в категории, включающие информацию осостоянии и поддерживающие наследование. Если говорить более подробно, по сравнению со строками, классы исключений: ° Лучше поддерживают возможные изменения в будущем — добавление новых исключений в будущем вообще не будет требовать изменений в инструкциях 1су.
° Предоставляют естественное место для хранения информации, доступной для обработчиков в инструкции ггу. Они могут включать как информацию о состоянии, так и методы, доступные через экземпляры класса. ° Позволяют исключениям принимать участие в иерархиях наследования с целью обладания общим поведением — наследовать методы отображения, например, чтобы обеспечить единый стиль сообщений об ошибках. Вследствие этих отличий исключения в виде классов лучше поддерживают возможность развития программ и крупных систем, чем исключения на основе строк. Строковые исключения на первый взгляд выглядят более простыми в использовании, пока программы имеют 734 Глава 28. Объекты исключений небольшой размер, но пользоваться ими становится значительно сложнее по мере роста размеров программ.
В действительности, по причинам, указанным выше, все встроенные исключения идентифицируются классами и организованы в виде дерева наследования. Вы можете избрать такой же подход при создании своих собственных исключений. В интересах обратной совместимости я представлю здесь и строковые исключения, и исключения на основе классов. В настоящее время могут использоваться оба вида исключений, однако строковые исключения генерируют предупреждения о нежелательности их использования в текущей версии Рув)топ (2.
б) и не будут поддерживаться в версии Рув)топ З.О. Мы будем их рассматривать, потому что они наверняка будут встречаться вам в существующем программном коде, но новые исключения, которые вам придется определять, уже в настоящее время должны оформляться в виде классов отчасти потому, что реализация на основе классов обладает неоспоримыми преимуществами, а отчасти потому, что вам едва ли захочется вносить изменения в свой программный код после выхода Руо)топ З.О. Исключения на основе строк Во всех примерах, что мы видели до сих пор, исключения, определяемые программой, были оформлены в виде строк. Это простейший способ создать исключение. Например: »> вуехс = "Му ехсвр11оп ввг1пд" »> тгу: гв1вв еувхс ...
вховрт еуехо: рг1п1 'оводлт' саодпт Любое строковое значение может использоваться для идентификации исключения. С технической точки зрения исключение идентифицируется строковым объектом, а не значением — вы должны использовать одно и то же имя переменной (то есть ссылку) при возбуждении н при перехвате исключения (подробнее об этой идее я расскажу в разделе, описывающем типичные проблемы, в конце седьмой части). В этом примере исключение вуехс — это обычная переменная, она может импортироваться другими модулями, и т. д. Текст строки практически не играет никакой роли за исключением того, что он выводится как текст сообщения об ошибке: »> гв1ве вуехо Тгасооаск (воат гвоепт св)1 1авт), Е>1Е т<отпто>", 11ПЕ т, тл О му еховрттоп втг1пд 735 Исключения на основе классов о Если учесть, что текст строковых исключений может выводиться на экран, вы наверняка предпочтете использовать более осмысленный текст, чем в примерах, показанных в этой книге.
Строковые исключения уходят в прошлое! Как уже упоминалось ранее, строковые исключения по-прежнему можно использовать, но в версии РусЬоп 2.5 они генерируют предупреждения и, как предполагается, вообще не будут поддерживаться в Ру1Ьоп З.О, если не раньше. Ниже приводится истинный результат работы предыдущего фрагмента в среде разработки ПН Е, входящей в состав Руд)топ 2.5: »> вуехо = 'Иу ехоер11оп всг1пд' »> сгу: гв1ве вуехо ехоерс вуехс: рг1пт 'оввдвс' 'ивгптпд (Ггоа ившьодв аосо1е).
Рт1е " ввтп ", 11пе 2 Оергеса(1опнвгп1пд: гв1втпд в втгтпд ехСЕРтюп >в Оергесвтес (Оергеов11опввгптпд: вовбухдение отроковвх исключений не приветствуется) сводит Вы можете запретить вывод таких предупреждений, но они выводятся для того, чтобы дать вам знать, что строковые исключения в будущем будут рассматриваться как ошибка и потому станут недопустимыми. В этой книге строковые исключения описываются лишь для того, чтобы дать вам возможность понимать программный код, написанный в прошлом; в настоящее время все встроенные исключения являются экземплярами классов, и все исключения, определяемые в программе, также должны создаваться на основе классов.
В следующем разделе объясняется — почему. Исключения на основе классов Строки обеспечивают самый простой способ определения исключений. Однако, как описывалось ранее, классы предоставляют дополнительные преимущества, которые заслуживают того, чтобы познакомиться с ними. Наиболее важное преимущество заключается в том, что классы позволяют организовать исключения в категории и они обладают большей гибкостью, чем простые строки. Кроме того, классы обеспечивают естественный способ присоединения к исключениям дополнительной информации и поддерживают наследование.
Они обеспечивают лучшее решение и поэтому в скором будущем будут представлять единственную возможность определения новых исключений. Помимо отличий в программном коде главное различие между строковыми исключениями и исключениями на базе классов заключается 736 Глаза 28. Объекты исключений в способе идентификации возбужденных исключений в предложениях ехсерт инструкции тгу: ° Строковые исключения идентифицируются по идентичности объекта: идентификация возбужденного исключения в предложении ехсерт выполняется с помощью оператора та (а не ==). ° Исключения на основе классов идентифицируются отношением наследования: возбужденное исключение считается соответствующим предложению ехсерт, если в данном предложении указан класс исключения или любой из его суперклассов. То есть, когда в инструкции 1гу предложение ехсерт содержит суперкласс, оно будет перехватывать экземпляры этого суперкласса, а также экземпляры всех его подклассов, расположенных ниже в дереве наследования.
Благодаря этому исключения на основе классов поддерживают возможность создания иерархий исключений: суперклассы превращаются в имена категорий, а подклассы становятся различными видами исключений внутри категории. Используя имя общего суперкласса, предложение ехсерт сможет перехватывать целую категорию исключений — каждый конкретный подкласс будет соответствовать этому предложению.
Пример исключения-класса Давайте рассмотрим на примере программного кода, как работают исключения-классы. В следующем файле с1аааехс.ру определяется суперкласс с именем Зепега1 и два подкласса с именами Зрес1(1с1 и Зрестг(с2. Этот пример иллюстрирует понятие категорий исключений, где 6епега1 — это имя категории, а два подкласса — это определенные типы исключений внутри категории. Обработчики, которые перехватывают исключение Зепега1, также будут перехватывать и все его подклассы, в том числе Зрес111с1 и Зрес((тс2: с1авв Вапага1. рава с1авв Зраотттс1(запага1): рава с1авв Зрасттто2(Сепага1): рава Оат га(вагр(): Х = Вапага1() гатов Х з Возбуидаег экзеипляр суперкласса исключения оег гатвег1 О; Х = Зраот(1о1() га1ва Х Ф Возбуидаег экзеипляр подкласса исключения В дополнение к этой идее исключения на основе классов обеспечивают лучшую поддержку информации о состоянии (присоединенной к экземплярам), и позволяют исключениям принимать участие в иерархиях наследования (с целью обрести общие черты поведения).
Они представляют собой более мощную альтернативу строковым исключениям при незначительном увеличении объемов программного кода. Исключения на основе классов бет гатвег2() Х = Ярестттс2() Ф брэбуадэет экэеилляр другого лрдклэрса исключения та!ее Х тсг Гипс !п (гагвегс, гатвег1, гвтвег2): !гу: гипс() ехсер! Сепега1; д Перехватывает исклачения Зелега1 и лабые его подклассы !арсг! вув рг1п! 'саары .', вув.ехс !и!оО(0) С:Хру!Поп> ру!Поп с1аввехс.ру сапряс: аа1п .Сепега1 сапрж: аатп .Брес1ттс1 сарры автп Брестттс2 Мы еще вернемся к используемой здесь функции вув ехс тп(с в следующей главе — с ее помощью можно получить информацию о самом последнем исключении. А пока коротко замечу, что первый элемент результата, возвращаемого функцией, — это имя класса возбужденного исключения, а второй — фактический экземпляр класса исключения.
Кроме этого метода нет никакого другого способа определить точно, что произошло, в пустом предложении ехсвр(, которое перехватывает все исключения. Обратите внимание, что здесь для использования в инструкциях татвв создаются экземпляры классов — как будет показано ниже в этом разделе, когда будет приводиться формализованное описание инструкции га1ве, при возбуждении исключений на основе классов всегда используются экземпляры. В этом фрагменте также присутствуют функции, которые возбуждают исключения всех трех классов, а кроме того на верхнем уровне имеется инструкция !гу, в которой производится вызов функций и осуществляется перехват исключения Селе та! (та же самая инструкция ! ту перехватывает и два более специфичных исключения, потому что они являются подклассами класса Селе га1). Еще одно замечание: текущая документация по языку Ру!Ьоп указывает, что предпочтительнее (но не обязательно) создавать свои собственные классы исключений, наследуя встроенный класс исключения с именем Ехсер!!оп.
Для этого нам придется переписать первую строку файла с!аээехс.ру, как показано ниже: с1аы Сепега1(Ехсер(1сп): рвы с1аы Зрес!11с1(6епега1): раве с1авв Зрес((тс2(6епега1). рввв Хотя это не является обязательным требованием, и в настоящее время отдельные классы прекрасно справляются с ролью исключений, тем не менее, этот предпочтительный способ со временем станет обязательным. Если вы хотите, чтобы ваш программный код был готов к будущим изменениям в языке, создавайте свои корневые суперклассы, наследуя класс Ехсер!1оп, как показано здесь. Кроме всего прочего, при 738 Глава 28. Объекты исключений таком подходе ваши классы приобретают по наследству некоторые полезные интерфейсы.