Главная страница
Навигация по странице:

  • Классы встроенных исключений

  • Определение текста исключения

  • Передача данных и поведения в экземплярах

  • Пример: передача дополнительных данных в исключениях на основе классов и строк

  • Математический анализ. 3е издание


    Скачать 4.86 Mb.
    Название3е издание
    АнкорМатематический анализ
    Дата04.02.2022
    Размер4.86 Mb.
    Формат файлаpdf
    Имя файлаpython_01.pdf
    ТипДокументы
    #351981
    страница90 из 98
    1   ...   86   87   88   89   90   91   92   93   ...   98
    python stringexc.py
    caught: general caught: specific1
    caught: specific2
    Однако в случае больших или глубоких иерархий исключений может оказаться гораздо проще перехватывать категории, используя классы,
    чем перечислять в предложении except все исключения, входящие в ка
    тегорию. Кроме того, иерархии категорий можно расширять, добавляя новые подклассы, не ломая при этом существующий программный код.
    Предположим, что вы занимаетесь разработкой библиотеки, реали
    зующей функции обработки числовой информации, которая использу
    ется широким кругом людей. Во время работы над библиотекой вы об
    наруживаете две ситуации, которые могут приводить к таким ошиб
    кам, как деление на ноль и переполнение. Вы описываете эти ошибки как исключения, которые могут возбуждаться этой библиотекой, и оп
    ределяете их как простые строки:
    # mathlib.py divzero = 'Division by zero error in library'
    oflow = 'Numeric overflow error in library'

    Исключения на основе классов
    739
    def func():
    raise divzero
    Теперь те, кто будет использовать вашу библиотеку, будут стремиться обертывать вызовы ваших функций или классов инструкцией try, что
    бы перехватывать два ваших исключения (если они не будут перехва
    тывать их, эти исключения будут приводить к аварийному заверше
    нию программ):
    # client.py import mathlib try:
    mathlib.func(...)
    except (mathlib.divzero, mathlib.oflow):
    ...вывод сообщения и восстановление после ошибки...
    Все работает просто замечательно и многие начинают использовать ва
    шу библиотеку. Однако шесть месяцев спустя вы обнаруживаете еще одну ситуацию, которая может приводить к другой ошибке – потере значимых разрядов, после чего добавляете новое строковое исключе
    ние:
    # mathlib.py divzero = 'Division by zero error in library'
    oflow = 'Numeric overflow error in library'
    uflow = 'Numeric underflow error in library'
    К сожалению, выпуском новой версии своей библиотеки вы создаете проблему для тех, кто ею пользуется. Если они явно указывали имена ваших исключений, теперь им придется вернуться к своим програм
    мам и внести соответствующие изменения везде, где производятся об
    ращения к вашей библиотеке, чтобы включить вновь добавленное имя исключения:
    # client.py import mathlib try:
    mathlib.func(...)
    except (mathlib.divzero, mathlib.oflow, mathlib.uflow):
    ...вывод сообщения и восстановление после ошибки...
    Вероятно, это не конец света. Если ваша библиотека предназначена исключительно для внутреннего использования, вы могли бы внести все необходимые изменения самостоятельно. Вы могли бы также на
    писать сценарий, который попытается ликвидировать проблему авто
    матически (едва ли такой сценарий будет насчитывать более дюжины строк, и на его создание уйдет совсем немного времени). Однако, если многим людям придется изменять свой программный код всякий раз,

    740
    Глава 28. Объекты исключений когда вы изменяете свой набор исключений, такая политика обновле
    ния определенно не будет расцениваться как самая вежливая.
    Ваши пользователи могут попытаться избежать этой ловушки, опре
    деляя пустые предложения except, которые перехватывают все исклю
    чения:
    # client.py try:
    mathlib.func(...)
    except: # Перехватывать все исключения
    ...вывод сообщения и восстановление после ошибки...
    Но при таком решении могут перехватываться посторонние исключе
    ния, даже такие, которые вызваны опечатками в именах переменных,
    ошибками работы с памятью, и исключения, генерируемые програм
    мой при завершении, а для вас было бы нежелательно, чтобы перехва
    ченные исключения ошибочно классифицировались как ошибки в биб
    лиотеке. Как правило, при обработке исключений чем больше опреде
    ленности, тем лучше (к этой идее мы еще вернемся в разделе с описа
    нием типичных проблем, в следующей главе).
    1
    Так как же быть? Исключения на основе классов полностью ликвиди
    руют эту проблему. Вместо того чтобы определять библиотечные ис
    ключения как простой набор строк, их можно оформить в виде дерева классов с одним общим суперклассом, охватывающим целую катего
    рию исключений:
    # mathlib.py class NumErr(Exception): pass class Divzero(NumErr): pass class Oflow(NumErr): pass def func():
    raise DivZero()
    1
    Как было предложено одним моим сообразительным студентом, в модуле библиотеки можно было бы определить кортеж, содержащий все исключе
    ния, которые могут быть возбуждены библиотекой. Тогда клиент мог бы импортировать этот кортеж и использовать его имя в предложении except,
    чтобы перехватывать все библиотечные исключения (вспомните, что при использовании кортежа в предложении except будут перехватываться все
    перечисленные в нем исключения). Когда позднее в библиотеку добавляет
    ся новое исключение, можно просто расширять экспортируемый кортеж.
    Такой прием будет работать, но тогда вам придется постоянно обновлять кортеж с именами исключений внутри модуля библиотеки. Кроме того, ис
    ключения, основанные на классах, несут в себе гораздо больше преиму
    ществ по сравнению со строковыми исключениями, чем простое деление на категории, – они поддерживают возможность присоединять информацию о состоянии, обладают методами и используют механизм наследования.

    Исключения на основе классов
    741
    При таком подходе пользователям вашей библиотеки достаточно бу
    дет указать общий суперкласс (то есть категорию), чтобы перехваты
    вать все исключения, возбуждаемые библиотекой, причем, как суще
    ствующие, так и те, что появятся в будущем:
    # client.py import mathlib try:
    mathlib.func(...)
    except mathlib.NumErr:
    ...вывод сообщения и восстановление после ошибки...
    Когда вы опять вернетесь к работе над библиотекой, новые исключения можно будет добавлять как новые подклассы от общего суперкласса:
    # mathlib.py class Uflow(NumErr): pass
    В результате программный код пользователей, перехватывающий ис
    ключения вашей библиотеки, останется работоспособным без каких+
    либо изменений
    . Вы свободно сможете добавлять, удалять и изменять исключения произвольным образом – пока клиенты используют имя суперкласса, они могут не беспокоиться об изменениях в вашем наборе исключений. Другими словами, исключения на основе классов лучше отвечают требованиям сопровождения, чем строки. Кроме того, ис
    ключения на основе классов могут поддерживать хранение информа
    ции о состоянии и наследование, чего не скажешь о строках – эти кон
    цепции мы будем исследовать на примере ниже в этой главе.
    Классы встроенных исключений
    Примеры в предыдущем разделе в действительности возникли не на пустом месте. Исключения, определяемые программой, могут быть представлены как в виде строк, так и в виде классов, однако все встро
    енные исключения, которые могут возбуждаться самим интерпретато
    ром Python, представляют собой объекты классов, а не строки. Кроме того, они организованы в неглубокую иерархию с общими суперклас
    сами категорий и подклассами определенных типов исключений,
    практически так же, как в примере выше.
    Все знакомые исключения, с которыми нам уже приходилось встре
    чаться (например, SyntaxError), в действительности являются обычными классами, доступными в виде встроенных имен (в модуле __builtin__)
    и в виде атрибутов модуля exceptions, входящего в состав стандартной библиотеки. Кроме того, в языке Python встроенные исключения орга
    низованы в иерархию с целью поддержки различных режимов пере
    хвата исключений. Например:

    742
    Глава 28. Объекты исключений
    Exception
    Корневой суперкласс всех исключений.
    StandardError
    Суперкласс всех встроенных исключений.
    ArithmeticError
    Суперкласс всех арифметических ошибок.
    OverflowError
    Подкласс, идентифицирующий конкретную арифметическую ошибку.
    И так далее. Подробнее познакомиться с этой структурой можно либо в справочном руководстве по библиотеке Python, либо в тексте справки в модуле exceptions (описание функции help приводится в главах 4 и 14):
    >>> import exceptions
    >>> help(exceptions)
    ...объемный текст справки опущен...
    Дерево встроенных классов позволяет определять, насколько конкрет
    ными или универсальными будут ваши обработчики исключений. На
    пример, встроенное исключение ArithmeticError – это суперкласс для таких более конкретных исключений, как OverflowError и ZeroDivision
    Error
    . Указав имя ArithmeticError в инструкции try, вы будете перехва
    тывать все арифметические ошибки, а указав имя OverflowError, вы бу
    дете перехватывать только ошибки определенного типа и никакие другие.
    Точно так же можно использовать StandardError, имя суперкласса всех встроенных исключений, для организации выбора между встроенны
    ми исключениями и исключениями, определяемыми программой, на
    пример:
    try:
    try:
    action()
    except StandardError:
    ...обработка встроенных исключений...
    except:
    ... обработка исключений, определяемых программой...
    else:
    ...обработка случая отсутствия исключения...
    Используя имя корневого класса Exception, можно очень близко ими
    тировать поведение пустого предложения except (которое перехваты
    вает любые исключения). Однако это будет не полная имитация, так как в этом случае не будут перехватываться строковые исключения и исключения на основе отдельных классов, которые пока не обяза
    тельно должны быть подклассами корневого класса Exception.

    Исключения на основе классов
    743
    Неважно, будете вы использовать категории в дереве встроенных классов или нет, этот подход служит отличным примером; при исполь
    зовании подобных методов к созданию своих собственных исключений вы сможете реализовать гибкие наборы исключений, которые легко можно изменять.
    Во всех других отношениях встроенные исключения практически не
    отличимы от первоначальной модели строковых исключений. Обычно вам даже не придется беспокоиться по поводу имеющихся различий,
    если, конечно, вы не исходите из предположения, что встроенные ис
    ключения являются строками, и не пытаетесь использовать их в опе
    рации конкатенации без необходимого преобразования (например, вы
    ражение KeyError + "spam" будет приводить к ошибке, а выражение str(KeyError) + "spam"
    вполне допустимо).
    Определение текста исключения
    Когда в начале главы мы знакомились с исключениями на основе строк, мы видели, что текст строки выводится в стандартный поток вывода сообщений об ошибках, когда исключение не обрабатывается программой (то есть, когда исключение достигает обработчика по умолчанию). Но какое сообщение будет выводиться в случае исключе
    нийклассов? По умолчанию выводится имя класса и не очень удобо
    читаемая информация об объекте экземпляра:
    >>> class MyBad: pass
    >>> raise MyBad()
    Traceback (most recent call last):
    File "
    ", line 1, in
    raise MyBad()
    MyBad: <__main__.MyBad instance at 0x00BB5468>
    Чтобы улучшить сообщение, необходимо переопределить в классе ис
    ключения метод __repr__ или __str__, чтобы возвращалась желаемая строка, которая будет отображаться, когда ваше исключение будет достигать обработчика по умолчанию:
    >>> class MyBad:
    ... def __repr__(self):
    ... return "Sorry my mistake!"
    >>> raise MyBad()
    Traceback (most recent call last):
    File "
    ", line 1, in
    raise MyBad()
    MyBad: Sorrymy mistake!
    Как мы уже знаем, метод перегрузки __repr__, используемый здесь, вы
    зывается при выводе и при преобразовании экземпляра класса в стро
    ку; метод __str__ реализует дружественное строковое представление,

    744
    Глава 28. Объекты исключений которому инструкции print отдают предпочтение. (Подробнее о мето
    дах преобразования в строку рассказывается в разделе «Перегрузка операторов» в главе 24.)
    Обратите внимание, что если наследовать встроенные классы исклю
    чений, как рекомендовалось выше, текст сообщения об ошибке немно
    го изменится – аргументы конструктора автоматически сохраняются в экземпляре и отображаются в тексте сообщения:
    >>> class MyBad(Exception): pass
    >>> raise MyBad()
    Traceback (most recent call last):
    File "
    ", line 1, in
    raise MyBad()
    MyBad
    >>> class MyBad(Exception): pass
    >>> raise MyBad('the', 'bright', 'side', 'of', 'life')
    Traceback (most recent call last):
    File "
    ", line 1, in
    raise MyBad('the', 'bright', 'side', 'of', 'life')
    MyBad: ('the', 'bright', 'side', 'of', 'life')
    Если ваши конечные пользователи могут видеть сообщения об ошиб
    ках, порождаемые исключениями, у вас наверняка появится желание определить собственные методы форматирования сообщений на основе перегрузки операторов, как показано здесь. Возможность автоматиче
    ского присоединения информации о состоянии к экземплярам – очень удобная особенность, о чем подробнее рассказывается в следующем разделе.
    Передача данных и поведения в экземплярах
    Помимо поддержки гибких иерархий классы исключений также яв
    ляются удобным местом для хранения дополнительной информации в виде атрибутов экземпляров. Когда возбуждается исключение на ос
    нове класса, вместе с исключением интерпретатор автоматически пе
    редает объект экземпляра класса в виде элемента дополнительных данных. Так же, как и в случае строковых исключений, вы можете по
    лучить доступ к экземпляру, указав дополнительную переменную в ин
    струкции try. Это обеспечивает естественный способ передачи допол
    нительных данных и функциональных возможностей обработчику ис
    ключения.
    Пример: передача дополнительных данных
    в исключениях на основе классов и строк
    Давайте рассмотрим возможность передачи дополнительных данных на примере и попутно сравним подходы, основанные на использовании классов и строк. Программа, выполняющая анализ файлов, может со

    Исключения на основе классов
    745
    общать об ошибке форматирования, возбуждая экземпляр исключе
    ния, который заполняется дополнительной информацией об ошибке:
    >>> class FormatError:
    ... def __init__(self, line, file):
    ... self.line = line
    ... self.file = file
    >>> def parser():
    ... # когда обнаруживается ошибка
    ... raise FormatError(42, file='spam.txt')
    >>> try:
    ... parser()
    ... except FormatError, X:
    ... print 'Error at', X.file, X.line
    Error at spam.txt 42
    В этом примере переменной X в предложении except присваивается ссылка на экземпляр, который был сгенерирован во время возбужде
    ния исключения.
    1
    Однако с практической точки зрения этот способ не имеет заметных преимуществ перед возможностью передачи составных объектов (например, кортежей, списков или словарей) в виде дополни
    тельных данных строковых исключений, и сам по себе не выглядит дос
    таточным побудительным мотивом к использованию исключений на основе классов. Ниже приводится эквивалентный фрагмент, в котором используются строковые исключения:
    >>> formatError = 'formatError'
    >>> def parser():
    ... # когда обнаруживается ошибка
    ... raise formatError, {'line':42, 'file':'spam.txt'}
    >>> try:
    ... parser()
    ... except formatError, X:
    ... print 'Error at', X['file'], X['line']
    Error at spam.txt 42
    На этот раз переменной X в предложении except присваивается словарь с дополнительной информацией, который передается инструкции
    1
    Как будет показано в следующей главе, доступ к объекту экземпляра клас
    са исключения обеспечивается также вторым элементом кортежа, возвра
    щаемого функцией sys.exc_info – инструментом, который возвращает ин
    формацию о самом последнем исключении. Если в предложении except не указано имя переменной, можно использовать эту функцию, когда возни
    кает необходимость обратиться к исключению за получением присоединен
    ных к нему данных или для вызова его методов.

    746
    Глава 28. Объекты исключений raise
    . Результат получается тот же самый, но при этом нет необходи
    мости писать определение класса. Однако подход, основанный на ис
    пользовании классов, может оказаться более удобным, когда исключе
    ние должно обладать еще и поведением. Класс исключения определяет еще и методы, которые можно вызывать из обработчика:
    class FormatError:
    def __init__(self, line, file):
    self.line = line self.file = file def logerror(self):
    log = open('formaterror.txt', 'a')
    print >> log, 'Error at', self.file, self.line def parser():
    raise FormatError(40, 'spam.txt')
    try:
    parser()
    except FormatError, exc:
    exc.logerror()
    При использовании классов методы (такие как loggeror) могут наследо
    ваться подклассами, а атрибуты экземпляра (такие как line и file)
    предоставляют возможность сохранения информации о состоянии,
    обеспечивая дополнительный контекст для последующих вызовов ме
    тодов. Мы могли бы имитировать этот эффект, передавая простые функции вместе со строковыми исключениями, но это приведет к чрез
    мерному усложнению программного кода:
    formatError = "formatError"
    def logerror(line, file):
    log = open('formaterror.txt', 'a')
    print >> log, 'Error at', file, line def parser():
    raise formatError, (41, 'spam.txt', logerror)
    try:
    parser()
    except formatError, data:
    data[2](data[0], data[1]) # Или просто: logerror()
    Естественно, такие функции не могут быть унаследованы как методы класса и не способны хранить информацию о состоянии в атрибутах экземпляра класса (lambdaвыражения и глобальные переменные – это самое лучшее, что можно использовать с функциями). Конечно, мож
    но было бы передать экземпляр класса в виде дополнительных данных строковых исключений, чтобы добиться того же самого эффекта, но если мы зашли так далеко, чтобы имитировать исключения на основе классов, лучше уж принять их – нам и так пришлось писать определе
    ние класса.

    Общие формы инструкции raise
    1   ...   86   87   88   89   90   91   92   93   ...   98


    написать администратору сайта