Математический анализ. 3е издание
Скачать 4.86 Mb.
|
python except finally.py caught IndexError finally run finally run finally run Traceback (most recent call last): File "C:/Python25/exceptfinally.py", line 9, in func() File "C:/Python25/exceptfinally.py", line 3, in raise2 def raise2(): raise SyntaxError SyntaxError: None Как мы видели в главе 27, начиная с версии Python 2.5, появилась воз можность использовать предложения except и finally в одной инструк ции try. Это делает описанный здесь прием синтаксического вложения ненужным, однако он попрежнему работает, его можно встретить в программном коде, написанном до выхода версии Python 2.5, и он может использоваться для реализации альтернативных конструкций обработки исключений. 756 Глава 29. Использование исключений Идиомы исключений Мы рассмотрели внутренний механизм исключений. Теперь рассмот рим некоторые другие типичные способы их использования. Исключения не всегда являются ошибками В языке Python все ошибки являются исключениями, но не все исклю чения являются ошибками. Например, в главе 9 мы видели, что по дос тижении конца файла метод чтения объекта файла возвращает пустую строку. Напротив, встроенная функция raw_input (с которой мы впер вые встретились в главе 3 и которую использовали в интерактивном цикле в главе 10) читает по одной строке текста при каждом вызове из стандартного потока ввода sys.stdin и возбуждает исключение EOFError по достижении конца файла. В отличие от методов объекта файла дан ная функция не возвращает пустую строку; пустая строка, полученная от функции raw_input, означает всего лишь пустую строку. Несмотря на свое название, исключение EOFError в данном контексте – это всего лишь сигнал, а не ошибка. По этой причине, чтобы избежать прежде временного завершения работы сценария, функцию raw_input обертыва ют инструкцией try, которую вкладывают в цикл, как показано ниже: while 1: try: line = raw_input() # Прочитать строку из потока stdin except EOFError: break # Выход по достижении конца файла else: ...обработка следующей строки... Существуют и другие встроенные исключения, которые являются сиг налами, а не ошибками. В языке Python имеется также ряд встроен ных исключений, которые являются скорее предупреждениями, чем ошибками. Некоторые из них применяются, чтобы сообщить о неже лательности использования некоторых особенностей языка (которые вскоре будут удалены). За дополнительной информацией по предупре ждениям обращайтесь к описанию встроенных исключений в руково дстве по стандартной библиотеке и к модулю warnings. Передача сигналов из функций по условию Исключения, определяемые программой, также могут служить сигна лами об условиях, которые не являются ошибками. Например, проце дура поиска может предусматривать возбуждение исключения в слу чае нахождения соответствия вместо того, чтобы возвращать флаг со стояния, который должен интерпретироваться вызывающей програм мой. В следующем примере инструкция try/except/else играет роль инструкции if/else, предназначенной для проверки возвращаемого значения: Идиомы исключений 757 class Found(Exception): pass def searcher(): if ...успех...: raise Found() else: return try: searcher() except Found: # Исключение, если элемент найден ...успех... else: # иначе: элемент не найден ...неудача... В более широком смысле такая организация программного кода мо жет с успехом использоваться для любой функции, которая не может вернуть специальный признак, свидетельствующий об успехе или не удаче. Например, если любое возвращаемое значение является допус тимым, невозможно выбрать какоето одно значение, которое сигна лизировало бы о необычных состояниях. Исключения обеспечивают способ подать сигнал, не возвращая значение: class Failure(Exception): pass def searcher(): if ...успех...: return ...найденный_элемент... else: raise Failure() try: item = searcher() except Failure: ...сообщение о неудаче... else: ...обработка найденного элемента... Поскольку язык Python является динамически типизированным и в сво ей основе поддерживает полиморфизм, исключения, а не возвращение специального признака, являются более предпочтительным способом сообщать о таких состояниях. Отладка с помощью внешних инструкций try Обработчики исключений можно также использовать как замену об работчика по умолчанию. Обернув всю программу (или вызов ее) во внешнюю инструкцию try, можно перехватывать любые исключения, которые только будут возникать во время работы программы, отменяя тем самым способ завершения программы, заданный по умолчанию. В следующем фрагменте пустое предложение except перехватывает любые необработанные исключения, возникшие в ходе выполнения программы. Чтобы получить доступ непосредственно к самому исклю 758 Глава 29. Использование исключений чению, вызовите встроенную функцию sys.exc_info из модуля sys – она возвращает кортеж, в котором первые два элемента содержат имя исключения и дополнительные данные (если имеются). Для исключе ний на основе классов эти два элемента представляют имя класса ис ключения и экземпляр класса возбужденного исключения соответст венно (вскоре мы подробнее рассмотрим функцию sys.exc_info): try: ...запуск программы... except: # Сюда попадут все необработанные исключения import sys print 'uncaught!', sys.exc_info()[0], sys.exc_info()[1] Этот прием часто используется во время разработки, так как он позво ляет сохранить программу активной даже после ошибки – он позволяет производить дополнительные проверки без необходимости перезапус кать программу. Это прием может также использоваться для тестиро вания другого программного кода, как описано в следующем разделе. Запуск тестов в рамках единого процесса Некоторые из приемов, которые мы только что рассмотрели, можно было бы объединить в тестовом приложении, которое позволяет тести ровать другой программный код в рамках одного и того же процесса: import sys log = open('testlog', 'a') from testapi import moreTests, runNextTest, testName def testdriver(): while moreTests(): try: runNextTest() except: print >> log, 'FAILED', testName(), sys.exc_info()[:2] else: print >> log, 'PASSED', testName() testdriver() Здесь функция testdriver выполняет в цикле серию тестов (модуль te stapi – некая абстракция в этом примере). Поскольку в обычной ситуа ции необработанное исключение приводило бы к завершению самого тестового приложения, можно обернуть вызовы очередного теста инст рукцией try, чтобы обеспечить продолжение процесса тестирования по сле неудачного завершения любого из тестов. Здесь, как обычно, пустое предложение except перехватывает любые необработанные исключения, возникшие в ходе выполнения теста, и регистрирует в файле информа цию об исключении, полученную с помощью функции sys.exc_info. Такой подход типичен для систем, которые тестируют функции, моду ли и классы, запуская их в рамках того же самого процесса, что и само Идиомы исключений 759 тестовое приложение. Однако на практике тестирование может ока заться процедурой гораздо более сложной, чем показано здесь. Напри мер, чтобы протестировать внешнюю программу, может потребоваться проверять коды состояния или вывод, создаваемый такими средствами запуска программ, как os.system и os.popen, описания которых вы най дете в стандартном руководстве по библиотеке (такие инструменты во обще не возбуждают исключений в случае появления ошибок во внеш ней программе – фактически тест выполняется параллельно с програм мой, выполняющей тестирование). В конце этой главы мы познакомимся с некоторыми законченными платформами, предназначенными для проведения тестов, такими как Doctest и PyUnit, которые обеспечивают возможность сравнения ожи даемого вывода с фактическими результатами. Подробнее о функции sys.exc_info Функция sys.exc_info, результаты которой использовались в послед них двух разделах, является предпочтительным способом доступа к по следнему возбужденному исключению. Если в момент ее вызова ника кое исключение не обрабатывается, функция возвращает кортеж с тре мя объектами None. В противном случае возвращаются (тип, значение, трассировочная_информация) , где: • Тип – это тип обрабатываемого исключения (объект класса для ис ключений на основе классов). • Значение – это параметр исключения (ассоциированное значение или второй аргумент инструкции raise, который всегда является экземпляром класса, если типом исключения является объект класса). • Трассировочная информация – это объект, который представляет стек вызовов в точке, где возникло исключение (в документации к модулю traceback описываются инструменты, которые могут ис пользоваться вместе с этим объектом для создания сообщений об ошибках вручную). Для извлечения типа и значения самого последнего исключения по прежнему могут использоваться более старые инструментальные сред ства, такие как sys.exc_type и sys.exc_value, но они могут использо ваться только применительно к единственному исключению, глобаль ному для всего процесса. В то время как более предпочтительная функция sys.exc_info запоминает информацию об исключениях в каж дом потоке выполнения. Конечно, это имеет значение только при ис пользовании нескольких потоков выполнения в программах на языке Python (тема, которая далеко выходит за рамки этой книги). За допол нительной информацией обращайтесь к справочному руководству по библиотеке языка Python и к другим специализированным книгам. 760 Глава 29. Использование исключений Советы по применению исключений Вообще говоря, исключения в языке Python очень просты в обраще нии. Настоящее искусство их использования заключается в принятии решения, насколько универсальными должны быть предложения ex cept и какой объем программного кода должен быть обернут инструк циями try. Рассмотрим сначала вторую проблему. Что должно быть обернуто В принципе, можно было бы обернуть каждую инструкцию в сценарии в свою собственную инструкцию try, но это будет выглядеть достаточ но глупо (тогда инструкции try тоже следовало бы обернуть в инструк ции try!). Это настоящая проблема проектирования, которая никак не связана с конкретным языком программирования и становится более очевидной на практике. Однако, ниже приводится несколько правил, выработанных на практике: • В инструкции try следует заворачивать операции, которые обычно терпят неудачу. Например, операции, взаимодействующие с систе мой (открытие файлов, взаимодействия с сокетами и т. д.), являют ся первыми кандидатами для заключения их в инструкции try. • При этом из первого правила есть исключение – в простых сценари ях бывает желательно, чтобы подобные неудачи приводили к завер шению работы программы. Это особенно верно, когда неудачи ожи даемы. Неудачи в языке Python приводят к выводу полезных сооб щений (только не в случае краха программы), и они часто представ ляют собой лучший результат, на который только можно надеяться. • Завершающие операции должны заключаться в инструкции try/ finally , чтобы гарантировать их выполнение. Эта форма инструк ции позволяет выполнять программный код независимо от того, возникло исключение или нет. • Иногда более удобно завернуть вызов крупной функции в единст венную инструкцию try, чем засорять эту функцию несколькими инструкциями try. При таком подходе все исключения, возникшие в функции, будут перехвачены инструкцией try, окружающей вы зов, за счет чего можно уменьшить объем программного кода внут ри самой функции. Влияние на количество обработчиков исключений нередко оказывает тип программы. Например, серверные программы должны работать постоянно, и поэтому в них инструкции try наверняка будут необходи мы, чтобы перехватывать исключения и выполнять восстановитель ные операции после них. В программах тестирования, таких как мы видели в этой главе, также необходимо выполнять обработку исключе ний. Однако в более простых сценариях часто можно вообще игнори ровать исключения, потому что неудача на любом этапе выполнения требует прекращения работы сценария. Советы по применению исключений 761 Не перехватывайте слишком много: избегайте пустых предложений except К вопросу о степени универсальности обработчика. Язык Python по зволяет явно указывать, какие исключения должны перехватываться, и иногда бывает необходимо проявлять осторожность, чтобы не пере хватывать слишком много. Например, вы уже знаете, что пустое пред ложение except перехватывает все исключения, которые только могут возникнуть в блоке try. Сделать это несложно и иногда даже желательно, но это может привес ти к тому, что будет перехвачена ошибка, обработка которой преду смотрена в инструкции try на более высоком уровне вложенной струк туры. В предлагаемом примере обработчик исключения перехватыва ет и деактивирует все исключения, которые достигнут его, независимо от того, ожидает ли какиелибо исключения обработчик уровнем выше: def func(): try: ... # Здесь возбуждается исключение IndexError except: ... # Но все исключения попадают сюда! try: func() except IndexError: # Исключение должно обрабатываться здесь Что еще хуже, такой программный код может перехватывать исклю чения, которые вообще не имеют никакого отношения к программе. Даже такие ситуации, как ошибки работы с памятью, настоящие ошибки в программном коде, остановки итераций и выход из програм мы, возбуждают исключения. Обычно такие исключения не должны перехватываться. Например, сценарии обычно завершают работу, когда поток управле ния достигает конца главного файла. Однако в языке Python имеется специальная функция sys.exit(statuscode), с помощью которой можно завершить работу программы. Чтобы завершить программу, эта функ ция в действительности возбуждает исключение SystemExit, благодаря чему имеется возможность предусмотреть возможность выполнения завершающих операций в инструкции try/finally, а в специализиро ванных программах – перехватить это событие. 1 По этой причине 1 Похожая функция os._exit также завершает работу программы, но делает это непосредственно – она пропускает этап выполнения завершающих дей ствий и не может быть перехвачена с помощью инструкций try/except или try /finally. Обычно эта функция используется в дочерних процессах, опи сание которых выходит далеко за рамки этой книги. За дополнительной информацией обращайтесь к справочному руководству по библиотеке язы ка Python и к другим специализированным книгам. 762 Глава 29. Использование исключений инструкция try с пустым предложением except может непреднамерен но перехватить такое важное исключение, как показано в следующем файле (exiter.py): import sys def bye(): sys.exit(40) # Серьезная ошибка: завершить работу программы немедленно! try: bye() except: print 'got it' # Ой! Мы проигнорировали команду на завершение print 'continuing...' % python exiter.py got it continuing... Вы просто не сможете предугадать все исключения, которые могут произойти во время выполнения операции. Вероятно, хуже всего то, что пустое предложение except может пере хватить настоящие ошибки в программном коде, которым желательно было бы позволить пройти дальше. Фактически пустые предложения except могут отключать механизм интерпретатора, предназначенный для вывода сообщений об ошибках, скрывая возможные ошибки в программном коде. Например, рассмотрим такой фрагмент: mydictionary = {...} try: x = myditctionary['spam'] # Ой: опечатка except: x = None # А мы предполагаем, что получили KeyError ...продолжение работы с x... Здесь программист предполагает, что в данной ситуации возможен единственный тип ошибки – это ошибка отсутствующего ключа. Но поскольку в имени словаря myditctionary была допущена опечатка (должно быть mydictionary), интерпретатор возбуждает исключение NameError , встретив ссылку на неопределенное имя, которое благопо лучно будет перехвачено и проигнорировано обработчиком. Обработ чик неправильно запишет в переменную значение по умолчанию, за маскировав ошибку в программе. Если этот программный код будет находиться достаточно далеко от места, где используется выбранное значение, его отладка превратится в весьма захватывающую задачу! Возьмите за правило специализировать свои обработчики, насколько это возможно – пустые предложения except удобны в использовании, но они потенциально опасны. Так, в последнем примере было бы луч ше использовать предложение except KeyError:, чтобы более явно обо значить свои намерения и избежать возможности перехвата посторон Советы по применению исключений 763 них событий. В более простых сценариях подобные проблемы могут иметь не такое существенное значение, чтобы перевесить удобство ис пользования, но в общем универсальные обработчики обычно достав ляют массу неприятностей. Не перехватывайте слишком мало: используйте категории С другой стороны, было бы нежелательно делать обработчики слиш ком узкоспециализированными. Когда в инструкции try перечисля ются конкретные исключения, перехватываться будут только те ис ключения, которые были перечислены. Это не обязательно плохо, но если в процессе развития программы появится новое исключение, вам может потребоваться вернуться и добавить это исключение в список обрабатываемых в своем программном коде. Например, следующий обработчик интерпретирует исключения myerror1 и myerror2 как нормальную ситуацию, а все остальные – как ошибку. Если в будущем будет добавлено исключение myerror3, оно будет обра батываться как ошибка, если не добавить его в список исключений: try: Except (myerror1, myerror2): # Работает неправильно при добавлении myerror3 ... # Нет ошибки else: ... # Рассматривается как ошибка К счастью, при осторожном использовании исключений на основе классов, обсуждавшихся в главе 28, можно полностью избавиться от этой ловушки. Как мы уже видели, если перехватывать общий супер класс, в будущем можно будет добавлять и возбуждать более конкрет ные подклассы исключений без необходимости изменять список ис ключений в предложении except; суперкласс становится легко расши ряемой категорией исключений: try: except SuccessCategoryName: # Работает правильно при добавлении myerror3 ... # Нет ошибки else: ... # Рассматривается как ошибка Если вы используете иерархии исключений на основе классов, порой придется пройти длинный путь, чтобы найти оптимальное решение. Мораль этой истории состоит в том, что вам следует с особым тщанием подходить к выбору степени детализации, чтобы обработчики исклю чений не были как слишком универсальными, так и слишком узкоспе циализированными. Политика исключений должна быть составной частью общего дизайна, особенно в крупных программах, |