Математический анализ. 3е издание
Скачать 4.86 Mb.
|
722 Глава 27. Основы исключений претатор всегда определяет появление ошибок во время выполнения программного кода. В следующем разделе будет показано, как возбуж дать исключения вручную. Инструкция raise Чтобы явно возбудить исключение, можно использовать инструкцию raise . В общем виде она имеет очень простую форму записи – инструк ция raise состоит из слова raise, за которым могут следовать имя воз буждаемого исключения и дополнительный элемент данных, переда ваемый вместе с исключением: raise raise raise # Повторно возбуждает самое последнее исключение Вторая форма позволяет передавать дополнительные данные вместе с исключением, чтобы сообщить обработчику о некоторых подробно стях. Данные, указанные после имени исключения в инструкции raise , передаются обратно в инструкцию try в виде переменной. Если инструкция try включает предложение except name, X:, то дополнитель ный элемент данных, указанный в инструкции raise, будет присвоен переменной X. Третья форма инструкции raise просто повторно возбу ждает текущее исключение – это удобно, когда возникает необходи мость передать перехваченное исключение другому обработчику. А что используется в качестве имени исключения? Это может быть имя встроенного исключения из встроенной области видимости (на пример, IndexError) или имя любого строкового объекта в программе. Это может быть также ссылка на класс или экземпляр класса – данная возможность позволяет еще больше обобщить формат инструкции raise . Я не буду касаться подробностей такого обобщения, пока мы не приступим к изучению классов исключений в следующей главе Независимо от того, какие исключения будут использованы, они всегда идентифицируются обычными объектами и только одно исключение может быть активным в каждый конкретный момент времени. Как только исключение перехватывается предложением except, находя щимся в любом месте программы, исключение деактивируется (то есть оно не будет передано другой инструкции try), если не будет повторно возбуждено при помощи инструкции raise или в результате ошибки. Пример: возбуждение и обработка собственных исключений Программы на языке Python с помощью инструкции raise могут воз буждать как встроенные, так и собственные исключения. В настоящее время собственные исключения в программе должны быть представле ны объектами экземпляров классов, как, например, MyBad в следую щем примере: Инструкция raise 723 class MyBad: pass def stuff(): raise MyBad() # Возбудить исключение вручную try: stuff() # Возбуждает исключение except MyBad: print 'got it' # Здесь выполняется обработка исключения ... # С этого места продолжается выполнение программы На этот раз исключение происходит внутри функции, но в действитель ности это не имеет никакого значения – управление немедленно пере дается блоку except. Обратите внимание, что инструкция try перехва тывает собственные исключения программы точно так же, как и встро енные исключения. Пример: передача дополнительных данных инструкции raise Как утверждалось выше, инструкция raise может передавать дополни тельные данные в месте с исключением для использования в обработ чике. Дополнительные данные позволяют передавать обработчику контекстную информацию об исключении. Например, если вы пишете программу анализа содержимого файла, то в случае нахождения ошиб ки можно было бы возбуждать исключение синтаксической ошибки и вместе с этим исключением передавать обработчику объект, вклю чающий в себя строку и информацию о файле (такой пример будет рас сматриваться в главе 28). Это бывает полезно, потому что после того, как исключение будет воз буждено, оно может быть перехвачено в другом модуле, то есть инст рукция raise, возбудившая исключение, и инструкция try, перехва тившая его, могут находиться в совершенно разных файлах. Поэтому вообще может оказаться невозможным использовать глобальные пере менные для сохранения дополнительной информации об исключении, потому что инструкция try может даже не знать, в каком модуле нахо дятся эти переменные. Возможность передачи данных непосредствен но с самим исключением обеспечивает более надежный доступ к ним в инструкции try. Строго говоря, дополнительные данные имеются у каждого исключения: как в случае с возвращаемыми значениями функций, если данные не были переданы явно, то в качестве таких данных используется специальный объект None. Следующий фраг мент, raisedata.py, иллюстрирует действие этой концепции на приме ре простых исключений на базе строк: myException = 'Error' # Строковый объект def raiser1(): raise myException, "hello" # Возбудить исключение, передать данные def raiser2(): 724 Глава 27. Основы исключений raise myException # Возбудить исключение, предполагается None def tryer(func): try: func() # Вызвать func except myException, extraInfo: # перехватить исключение с данными print 'got this:', extraInfo % python >>> from raisedata import * >>> tryer(raiser1) # Данные были переданы явно got this: hello >>> tryer(raiser2) # Дополнительные данные None по умолчанию got this: None Здесь функция tryer всегда запрашивает получение объекта с допол нительными данными – он поступает из функции raiser1 в виде явно заданной строки, а из функции raiser2 – в виде объекта None, исполь зуемого по умолчанию. В следующей главе мы увидим, что тот же самый прием может исполь зоваться для организации доступа к экземплярам при использовании исключений на базе классов – в этом случае переменной в предложе нии except присваивается ссылка на экземпляр класса исключения, который обеспечивает доступ к присоединенной информации и к мето дам класса. Пример: повторное возбуждение исключений с помощью инструкции raise Инструкция raise, в которой отсутствует имя исключения или допол нительные данные, просто повторно возбуждает текущее исключение. В таком виде она обычно используется, когда необходимо перехватить и обработать исключение, но при этом не требуется деактивировать ис ключение: >>> try: ... raise IndexError, 'spam' ... except IndexError: ... print 'propagating' ... raise propagating Traceback (most recent call last): File " IndexError: spam При таком использовании инструкция raise повторно возбуждает ис ключение, которое затем передается обработчику более высокого уров ня или обработчику по умолчанию, который останавливает выполне ние программы и выводит стандартное сообщение об ошибке. Инструкция assert 725 Инструкция assert Язык Python включает инструкцию assert в качестве особого случая возбуждения исключений. Это сокращенная форма типичного шабло на использования инструкции raise, которая представляет собой ус+ ловную инструкцию raise. Инструкция в форме: assert представляет собой эквивалент следующего фрагмента: if __debug__: if not raise AssertionError, Другими словами, если условное выражение возвращает ложное зна чение, интерпретатор возбуждает исключение: элемент данных (если присутствует) играет роль дополнительных данных исключения. Как и все исключения, исключение AssertionError приводит к завершению программы, если не будет перехвачено инструкцией try. Существует дополнительная возможность удалить все инструкции as sert из скомпилированного байткода программы за счет использования флага командной строки O при запуске интерпретатора и тем самым оп тимизировать программу. Исключение AssertionError является встроен ным исключением, а имя __debug__ – встроенным флагом, который авто матически получает значение 1 (истина), когда не используется флаг O. Пример: проверка соблюдения ограничений (но не ошибок) Обычно инструкция assert используется для проверки условий выпол нения программы во время разработки. При отображении в текст сооб щений об ошибках, полученных в результате выполнения инструкции assert , автоматически включается информация из строки исходного программного кода и значения, перечисленные в инструкции. Рас смотрим файл asserter.py: def f(x): assert x < 0, 'x must be negative' return x ** 2 % python >>> import asserter >>> asserter.f(1) Traceback (most recent call last): File " File "asserter.py", line 2, in f assert x < 0, 'x must be negative' AssertionError: x must be negative Важно не забывать, что инструкция assert главным образом предна значена для проверки соблюдения ограничений, накладываемых про 726 Глава 27. Основы исключений граммистом, а не для перехвата настоящих ошибок. Так как интер претатор Python в состоянии сам выявлять ошибки во время выполне ния программы, обычно нет необходимости использовать assert для выявления таких проблем, как выход индекса за допустимые преде лы, несоответствие типов или деление на ноль: def reciprocal(x): assert x != 0 # Бесполезная инструкция assert! return 1 / x # Интерпретатор автоматически проверит на равенство нулю Такие инструкции assert являются лишними, потому что встретив ошибку, интерпретатор автоматически возбудит исключение и вы вполне могли бы положиться в этом на него. 1 Еще один пример типич ного использования инструкции assert приводится в примере абст рактного суперкласса в главе 24 – там инструкция assert использова лась для того, чтобы вызов неопределенных методов приводил к ис ключению с определенным текстом сообщения. Контекстные менеджеры with/as В версии Python 2.6 (на момент, когда писалась эта книга, данная вер сия еще не вышла) появится новая инструкция, имеющая отношение к исключениям – with, с необязательным предложением as. Эта инст рукция предназначена для работы с объектами контекстных менедже ров, которые поддерживают новый протокол взаимодействия, осно ванный на использовании методов. Проще говоря, инструкция with/as может использоваться как альтер натива известной идиомы try/finally; подобно этой инструкции она предназначена для выполнения завершающих действий независимо от того, возникло ли исключение на этапе выполнения основного дей ствия. Однако, в отличие от инструкции try/finally, инструкция with поддерживает более богатый возможностями протокол, позволяющий определять как предварительные, так и заключительные действия для заданного блока программного кода. Язык Python дополняет некоторые встроенные средства контекстны ми менеджерами, например, файлы, которые закрываются автомати чески, или блокировки потоков выполнения, которые автоматически запираются и отпираются. Однако программист также может созда вать с классами и свои контекстные менеджеры. 1 По крайней мере, в большинстве случаев. Как предлагалось в четвертой части книги, проверка на наличие ошибки может использоваться в функ ции, выполняющей расчеты длительное время или необратимые действия. Но даже в этом случае старайтесь не использовать чрезмерно специализи рованные или чрезмерно ограничительные проверки, т. к. в противном случае это ограничит область применения вашего программного кода. Контекстные менеджеры with/as 727 Основы использования Данная функциональная возможность официально станет частью язы ка Python, только начиная с версии 2.6. В Python 2.5 по умолчанию она еще не доступна – ее следует активировать с помощью специаль ной инструкции импорта, с которой мы уже встречались в этой книге при изучении модулей (так как появятся два новых зарезервирован ных слова with и as, это новшество, как обычно, вводится постепенно): from __future__ import with_statement Когда эта инструкция import выполняется в Python 2.5, появляется возможность использовать новую инструкцию with и два новых заре зервированных слова. Основная форма инструкции with выглядит, как показано ниже: with выражение [as переменная]: блок with Здесь предполагается, что выражение возвращает объект, поддержи вающий протокол контекстного менеджера (вскоре я расскажу об этом протоколе подробнее). Этот объект может возвращать значение, кото рое будет присвоено переменной, если присутствует необязательное предложение as. Обратите внимание, что результат выражения не присваивается перемен ной – результатом выражения является объект, который поддерживает контекстный протокол, а переменной может быть присвоено некоторое другое значение. Объект, возвращаемый выражением, может затем выпол нять предварительные действия перед тем, как будет запущен блок with, а также завершающие действия после того, как этот блок будет выпол нен, независимо от того, было ли возбуждено исключение при его вы полнении. Некоторые встроенные объекты языка Python были дополнены под держкой протокола управления контекстом и потому могут использо ваться в инструкции with. Например, объекты файлов снабжены ме неджером контекста, который автоматически закрывает файл после выполнения блока with независимо от того, было ли возбуждено ис ключение при его выполнении: with open(r'C:\python\scripts') as myfile: for line in myfile: print line line = line.replace('spam', 'SPAM') ...остальной программный код... Здесь вызываемая функция open возвращает объект файла, который присваивается имени myfile. Применительно к переменной myfile мы можем использовать обычные средства, предназначенные для работы с файлами, – в данном случае с помощью итератора выполняется чте ние строки за строкой в цикле for. 728 Глава 27. Основы исключений Однако данный объект поддерживает протокол управления контек стом, используемый инструкцией with. После того как инструкция with начнет выполнение, механизм управления контекстом гарантиру ет, что объект файла, на который ссылается переменная myfile, будет закрыт автоматически, даже если в цикле for во время обработки фай ла произойдет исключение. Мы не будем рассматривать в этой книге многопоточную модель вы полнения в языке Python (за дополнительной информацией по этой те ме вам следует обращаться к книгам, посвященным прикладному про граммированию, таким как «Programming Python»), но блокировка и средства синхронизации посредством условных переменных также поддерживаются инструкцией with за счет обеспечения поддержки протокола управления контекстом: lock = threading.Lock() with lock: # Критическая секция программного кода ...доступ к совместно используемым ресурсам... Здесь механизм управления контекстом гарантирует, что блокировка автоматически будет приобретена до того, как начнет выполняться блок, и освобождена по завершении работы блока. Модуль decimal (подробнее о числах с фиксированной точностью пред ставления рассказывается в главе 5) также использует менеджеры контекста для упрощения сохранения и восстановления текущего контекста вычислений, определяющего параметры точности и округ ления, используемые в вычислениях. Протокол управления контекстом Интерфейс, который должны реализовать объекты для использования совместно с инструкцией with, достаточно сложен, хотя большинству программистов достаточно лишь знать, как используются существую щие контексты менеджеров. Однако разработчикам программных ин струментов может потребоваться знание правил создания новых ме неджеров, поэтому коротко рассмотрим основные принципы. Ниже описывается, как в действительности работает инструкция with: 1. Производится вычисление выражения, возвращающего объект, из вестный как менеджер контекста, который должен иметь методы __enter__ и __exit__. 2. Вызывается метод __enter__ менеджера контекста. Возвращаемое значение метода присваивается переменной – при наличии предло жения as, в противном случае оно просто уничтожается. 3. Затем выполняется блок программного кода, вложенный в инст рукцию with. 4. Если при выполнении блока возбуждается исключение, вызывает ся метод __exit__(тип, значение, диагностическая_информация), которо Контекстные менеджеры with/as 729 му передается подробная информация об исключении. Обратите внимания, что это те же самые значения, которые возвращает функция sys.exc_info, описываемая в руководстве по языку Python и далее в этой книге. Если этот метод возвращает ложное значение, исключение возбуждается повторно, в противном случае исключе ние деактивируется. Обычно исключение следует возбуждать по вторно, чтобы оно могло выйти за пределы инструкции with. 5. Если в блоке with исключение не возникает, метод __exit__ все рав но вызывается, но в аргументах тип, значение и диагностическая_ин формация ему передается значение None. Рассмотрим небольшой пример, демонстрирующий работу протокола. Следующий фрагмент определяет объект менеджера контекста, кото рый сообщает о входе и выходе из блока программного кода любой ин струкции with, с которой он используется: from __future__ import with_statement # Требуется в Python 2.5 class TraceBlock: def message(self, arg): print 'running', arg def __enter__(self): print 'starting with block' return self def __exit__(self, exc_type, exc_value, exc_tb): if exc_type is None: print 'exited normally\n' else: print 'raise an exception!', exc_type return False # propagate with TraceBlock() as action: action.message('test 1') print 'reached' with TraceBlock() as action: action.message('test 2') raise TypeError print 'not reached' Обратите внимание, что метод __exit__ должен возвращать False, чтобы разрешить дальнейшее распространение исключения – отсутствие ин струкции return обеспечивает тот же самый эффект, потому что в этом случае по умолчанию возвращается значение None, которое по опреде лению является ложным. Кроме того, следует заметить, что метод __enter__ возвращает сам объект self, который присваивается пере менной в предложении as; при желании этот метод может возвращать совершенно другой объект. При запуске этого фрагмента менеджер контекста с помощью своих методов __enter__ и __exit__ отмечает моменты входа и выхода из блока инструкции with: |