Математический анализ. 3е издание
Скачать 4.86 Mb.
|
730 Глава 27. Основы исключений % python withas.py starting with block running test 1 reached exited normally starting with block running test 2 raise an exception! Traceback (most recent call last): File "C:/Python25/withas.py", line 22, in raise TypeError TypeError Менеджеры контекста являются новейшими инструментами, которые официально еще не стали частью языка Python, поэтому мы не будем рассматривать здесь дополнительные подробности (за полной информа цией обращайтесь к стандартным руководствам по языку; например, новый стандартный модуль contextlib содержит дополнительные сред ства, которые могут использоваться для создания менеджеров контек стов). В более простых случаях инструкция try/finally обеспечивает достаточную поддержку для выполнения завершающих действий. Придется держать в уме: проверка ошибок Один из способов увидеть, насколько полезными могут быть ис ключения, состоит в том, чтобы сравнить стили программирова ния на языке Python и на языке, не имеющем исключений. На пример, если вы хотите написать надежную программу на языке C, вам потребуется проверять возвращаемые значения или коды состояния после выполнения каждой операции, которая может быть выполнена с ошибкой, и передавать результаты проверок в ходе выполнения программы: doStuff() { # Программа на языке C if (doFirstThing() == ERROR) # Проверить наличие ошибки return ERROR; # даже если здесь она не обрабатывается if (doNextThing() == ERROR) return ERROR; return doLastThing(); } main() { if (doStuff() == ERROR) badEnding(); В заключение 731 В заключение В этой главе мы приступили к изучению вопросов обработки исключе ний и к исследованию инструкций, связанных с исключениями: инст рукция try используется для перехвата исключений, raise используется для их возбуждения, assert используется для возбуждения исключений по условию и with используется для обертывания программного кода менеджерами контекстов, определяющими действия на входе и выходе. Пока исключения выглядят достаточно простым инструментом, впро чем, таковым они и являются, – единственная сложность заключается в их идентификации. Следующая глава продолжит наши исследова ния описанием реализации наших собственных объектов исключений, где будет показано, что классы позволяют создавать исключения более else goodEnding(); } Фактически в настоящих программах на языке C значительная доля всего программного кода выполняет проверку наличия ошибок. Но в языке Python такая настойчивость и методичность не требуется. Достаточно просто обернуть произвольные участки программы обработчиками исключений и писать эти участки в предположении, что никаких ошибок возникать не будет: def doStuff(): # Программный код на языке Python doFirstThing() # Нас не беспокоят возможные исключения, doNextThing() # поэтому можно не выполнять проверку doLastThing() if__name__ == '__main__': try: doStuff() # Здесь нас интересуют возможные результаты, except: # поэтому это единственное место, где нужна проверка badEnding() else: goodEnding Так как в случае исключения управление немедленно будет пере дано обработчику, здесь нет никакой необходимости разбрасывать проверки по всему программному коду, чтобы обезопасить себя от ошибок. Кроме того, благодаря тому, что интерпретатор Python автоматически обнаруживает ошибки, ваши программы обычно не требуют выполнять подобные проверки вообще. Таким обра зом, исключения позволяют в значительной степени игнорировать возможные необычные ситуации и отказаться от использования программного кода, выполняющего проверки на наличие ошибок. 732 Глава 27. Основы исключений полезные, чем простые строки. Однако, прежде чем двинуться вперед, ответьте на контрольные вопросы по темам, охваченным в этой главе. Закрепление пройденного Контрольные вопросы 1. Для чего служит инструкция try? 2. Какие две основные разновидности инструкции try существуют? 3. Для чего служит инструкция raise? 4. Для чего служит инструкция assert и какую другую инструкцию она напоминает? 5. Для чего служит инструкция with/as и какие другие инструкции она напоминает? Ответы 1. Инструкция try служит для перехвата исключений и проведения восстановительных действий после них. Она определяет блок вы полняемого программного кода и один или более обработчиков ис ключений, которые могут возникнуть в ходе выполнения блока. 2. Существует две основные разновидности инструкции try – это try/ex cept /else (используется для перехвата исключений) и try/finally (ис пользуется для указания завершающих действий, которые должны быть выполнены независимо от того, возникло ли исключение или нет). В версии Python 2.4 это две отдельные инструкции, которые можно объединить вложением друг в друга. В версии 2.5 и выше бло ки except и finally могут смешиваться в одной и той же инструкции, то есть две формы инструкции объединены в одну. В объединенной форме блок finally попрежнему выполняется при выходе из инст рукции try независимо от того, было обработано исключение или нет. 3. Инструкция raise возбуждает (запускает) исключение. Интерпре татор посредством внутренних механизмов возбуждает встроенные исключения, а ваши сценарии с помощью инструкции raise могут возбуждать как встроенные, так и свои собственные исключения. 4. Инструкция assert возбуждает исключение AssertionError, когда ус ловное выражение возвращает ложное значение. Она напоминает инструкцию raise, обернутую инструкций if. 5. Инструкция with/as предназначена для автоматического запуска программного кода, выполняющего предварительные и завершаю щие действия перед входом и после выхода из обернутого блока программного кода. Она в общих чертах напоминает инструкцию try /finally, так как тоже выполняет действия на выходе независи мо от того, возникло исключение или нет, но в отличие от послед ней позволяет определять действия на входе и на выходе, исполь зуя для этого протокол, основанный на использовании объектов. 28 Объекты исключений До сих пор я преднамеренно умалчивал о том, чем в действительности являются исключения. В языке Python понятие исключения было обобщено – как уже упоминалось в предыдущей главе, они могут идентифицироваться строковыми объектами или объектами экземп ляра класса. В настоящее время предпочтительнее использовать объ екты экземпляров класса, но в скором будущем они станут обязатель ными. У обоих подходов имеются свои достоинства, но классы обеспе чивают лучшее решение, когда дело доходит до поддержки иерархий исключений. Проще говоря, исключения на основе классов позволяют создавать ис ключения, организованные в категории, включающие информацию о состоянии и поддерживающие наследование. Если говорить более подробно, по сравнению со строками, классы исключений: • Лучше поддерживают возможные изменения в будущем – добавле ние новых исключений в будущем вообще не будет требовать изме нений в инструкциях try. • Предоставляют естественное место для хранения информации, дос тупной для обработчиков в инструкции try. Они могут включать как информацию о состоянии, так и методы, доступные через эк земпляры класса. • Позволяют исключениям принимать участие в иерархиях наследо вания с целью обладания общим поведением – наследовать методы отображения, например, чтобы обеспечить единый стиль сообще ний об ошибках. Вследствие этих отличий исключения в виде классов лучше поддер живают возможность развития программ и крупных систем, чем ис ключения на основе строк. Строковые исключения на первый взгляд выглядят более простыми в использовании, пока программы имеют 734 Глава 28. Объекты исключений небольшой размер, но пользоваться ими становится значительно сложнее по мере роста размеров программ. В действительности, по причинам, указанным выше, все встроенные исключения идентифи цируются классами и организованы в виде дерева наследования. Вы можете избрать такой же подход при создании своих собственных ис ключений. В интересах обратной совместимости я представлю здесь и строковые исключения, и исключения на основе классов. В настоящее время мо гут использоваться оба вида исключений, однако строковые исключе ния генерируют предупреждения о нежелательности их использова ния в текущей версии Python (2.5) и не будут поддерживаться в версии Python 3.0. Мы будем их рассматривать, потому что они наверняка бу дут встречаться вам в существующем программном коде, но новые ис ключения, которые вам придется определять, уже в настоящее время должны оформляться в виде классов отчасти потому, что реализация на основе классов обладает неоспоримыми преимуществами, а отчасти потому, что вам едва ли захочется вносить изменения в свой про граммный код после выхода Python 3.0. Исключения на основе строк Во всех примерах, что мы видели до сих пор, исключения, определяе мые программой, были оформлены в виде строк. Это простейший спо соб создать исключение. Например: >>> myexc = "My exception string" >>> try: ... raise myexc ... except myexc: ... print 'caught' Caught Любое строковое значение может использоваться для идентификации исключения. С технической точки зрения исключение идентифициру ется строковым объектом, а не значением – вы должны использовать одно и то же имя переменной (то есть ссылку) при возбуждении и при перехвате исключения (подробнее об этой идее я расскажу в разделе, описывающем типичные проблемы, в конце седьмой части). В этом примере исключение myexc – это обычная переменная, она может им портироваться другими модулями, и т. д. Текст строки практически не играет никакой роли за исключением того, что он выводится как текст сообщения об ошибке: >>> raise myexc Traceback (most recent call last): File " My exception string Исключения на основе классов 735 Если учесть, что текст строковых исключений может выводиться на экран, вы наверняка предпочтете использовать более осмыс ленный текст, чем в примерах, показанных в этой книге. Строковые исключения уходят в прошлое! Как уже упоминалось ранее, строковые исключения попрежнему можно использовать, но в версии Python 2.5 они генерируют предупре ждения и, как предполагается, вообще не будут поддерживаться в Py thon 3.0, если не раньше. Ниже приводится истинный результат рабо ты предыдущего фрагмента в среде разработки IDLE, входящей в со став Python 2.5: >>> myexc = 'My exception string' >>> try: raise myexc except myexc: print 'caught' Warning (from warnings module): File "__main__", line 2 DeprecationWarning: raising a string exception is deprecated (DeprecationWarning: возбуждение строковых исключений не приветствуется) caught Вы можете запретить вывод таких предупреждений, но они выводятся для того, чтобы дать вам знать, что строковые исключения в будущем будут рассматриваться как ошибка и потому станут недопустимыми. В этой книге строковые исключения описываются лишь для того, что бы дать вам возможность понимать программный код, написанный в прошлом; в настоящее время все встроенные исключения являются экземплярами классов, и все исключения, определяемые в программе, также должны создаваться на основе классов. В следующем разделе объясняется – почему. Исключения на основе классов Строки обеспечивают самый простой способ определения исключений. Однако, как описывалось ранее, классы предоставляют дополнитель ные преимущества, которые заслуживают того, чтобы познакомиться с ними. Наиболее важное преимущество заключается в том, что клас сы позволяют организовать исключения в категории и они обладают большей гибкостью, чем простые строки. Кроме того, классы обеспе чивают естественный способ присоединения к исключениям дополни тельной информации и поддерживают наследование. Они обеспечива ют лучшее решение и поэтому в скором будущем будут представлять единственную возможность определения новых исключений. Помимо отличий в программном коде главное различие между строко выми исключениями и исключениями на базе классов заключается 736 Глава 28. Объекты исключений в способе идентификации возбужденных исключений в предложени ях except инструкции try: • Строковые исключения идентифицируются по идентичности объ+ екта : идентификация возбужденного исключения в предложении except выполняется с помощью оператора is (а не ==). • Исключения на основе классов идентифицируются отношением наследования : возбужденное исключение считается соответствую щим предложению except, если в данном предложении указан класс исключения или любой из его суперклассов. То есть, когда в инструкции try предложение except содержит супер класс, оно будет перехватывать экземпляры этого суперкласса, а так же экземпляры всех его подклассов, расположенных ниже в дереве на следования. Благодаря этому исключения на основе классов поддер живают возможность создания иерархий исключений: суперклассы превращаются в имена категорий, а подклассы становятся различны ми видами исключений внутри категории. Используя имя общего су перкласса, предложение except сможет перехватывать целую катего рию исключений – каждый конкретный подкласс будет соответство вать этому предложению. В дополнение к этой идее исключения на основе классов обеспечивают лучшую поддержку информации о состоянии (присоединенной к эк земплярам), и позволяют исключениям принимать участие в иерархи+ ях наследования (с целью обрести общие черты поведения). Они пред ставляют собой более мощную альтернативу строковым исключениям при незначительном увеличении объемов программного кода. Пример исключениякласса Давайте рассмотрим на примере программного кода, как работают ис ключенияклассы. В следующем файле classexc.py определяется су перкласс с именем General и два подкласса с именами Specific1 и Specific2. Этот пример иллюстрирует понятие категорий исключе ний, где General – это имя категории, а два подкласса – это определен ные типы исключений внутри категории. Обработчики, которые пере хватывают исключение General, также будут перехватывать и все его подклассы, в том числе Specific1 и Specific2: class General: pass class Specific1(General): pass class Specific2(General): pass def raiser0(): X = General() # Возбуждает экземпляр суперкласса исключения raise X def raiser1(): X = Specific1() # Возбуждает экземпляр подкласса исключения raise X Исключения на основе классов 737 def raiser2(): X = Specific2() # Возбуждает экземпляр другого подкласса исключения raise X for func in (raiser0, raiser1, raiser2): try: func() except General: # Перехватывает исключения General и любые его подклассы import sys print 'caught:', sys.exc_info()[0] C:\python> python classexc.py caught: __main__.General caught: __main__.Specific1 caught: __main__.Specific2 Мы еще вернемся к используемой здесь функции sys.exc_info в сле дующей главе – с ее помощью можно получить информацию о самом последнем исключении. А пока коротко замечу, что первый элемент результата, возвращаемого функцией, – это имя класса возбужденно го исключения, а второй – фактический экземпляр класса исключе ния. Кроме этого метода нет никакого другого способа определить точ но, что произошло, в пустом предложении except, которое перехваты вает все исключения. Обратите внимание, что здесь для использования в инструкциях raise создаются экземпляры классов – как будет показано ниже в этом раз деле, когда будет приводиться формализованное описание инструк ции raise, при возбуждении исключений на основе классов всегда ис пользуются экземпляры. В этом фрагменте также присутствуют функ ции, которые возбуждают исключения всех трех классов, а кроме того на верхнем уровне имеется инструкция try, в которой производится вызов функций и осуществляется перехват исключения General (та же самая инструкция try перехватывает и два более специфичных исклю чения, потому что они являются подклассами класса General). Еще одно замечание: текущая документация по языку Python указы вает, что предпочтительнее (но не обязательно) создавать свои собст венные классы исключений, наследуя встроенный класс исключения с именем Exception. Для этого нам придется переписать первую строку файла classexc.py, как показано ниже: class General(Exception): pass class Specific1(General): pass class Specific2(General): pass Хотя это не является обязательным требованием, и в настоящее время отдельные классы прекрасно справляются с ролью исключений, тем не менее, этот предпочтительный способ со временем станет обязатель ным. Если вы хотите, чтобы ваш программный код был готов к буду щим изменениям в языке, создавайте свои корневые суперклассы, на следуя класс Exception, как показано здесь. Кроме всего прочего, при 738 Глава 28. Объекты исключений таком подходе ваши классы приобретают по наследству некоторые по лезные интерфейсы. Например, класс Exception имеет конструктор __init__ , который автоматически присоединяет к экземплярам свои аргументы. В чем преимущества классов исключений? Поскольку в примере предыдущего раздела имеется всего три возмож ных исключения, он действительно не может продемонстрировать все преимущества применения классов исключений. На самом деле мы могли бы достичь того же эффекта, указав в предложении except спи сок имен строковых исключений в круглых скобках. Как это можно сделать, показано в файле stringexc.py: General = 'general' Specific1 = 'specific1' Specific2 = 'specific2' def raiser0(): raise General def raiser1(): raise Specific1 def raiser2(): raise Specific2 for func in (raiser0, raiser1, raiser2): try: func() except (General, Specific1, Specific2): # Перехватывает любые из этих import sys print 'caught:', sys.exc_info()[0] C:\python> |