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

  • Рис. 4.1.

  • Рис. 4.2.

  • Возбуждение исключений

  • Собственные исключения

  • Программирование на Python 3. Руководство издательство СимволПлюс


    Скачать 3.74 Mb.
    НазваниеРуководство издательство СимволПлюс
    Дата10.11.2022
    Размер3.74 Mb.
    Формат файлаpdf
    Имя файлаПрограммирование на Python 3.pdf
    ТипРуководство
    #780382
    страница24 из 74
    1   ...   20   21   22   23   24   25   26   27   ...   74
    Обработка исключений
    Об ошибках и исключительных ситуациях интерпретатор Python сооб
    щает посредством возбуждения исключений, хотя в некоторых биб
    лиотеках сторонних разработчиков еще используются устаревшие приемы, такие как возврат «ошибочного» значения.
    Функция
    enume
    rate()
    ,
    стр. 166

    Обработка исключений
    193
    Перехват и возбуждение исключений
    Перехватывать исключения можно с помощью блоков try ... except,
    которые имеют следующий синтаксис:
    try:
    try_suite
    except exception_group1 as variable1:
    except_suite1

    except exception_groupN as variableN:
    except_suiteN
    else:
    else_suite
    finally:
    finally_suite
    Эта конструкция должна содержать хотя бы один блок except, а блоки else и finally являются необязательными. Блок else_suite выполняет
    ся, только если блок try_suite завершается обычным способом, и не выполняется в случае возникновения исключения. Если блок finally присутствует, он выполняется всегда и в последнюю очередь.
    Каждая группа exception_group в предложении except может быть един
    ственным исключением или кортежем исключений в круглых скобках.
    Часть as variable в каждой группе является необязательной. В случае ее использования в переменную variable записывается ссылка на ис
    ключение, которое возникло, благодаря этому к нему можно будет об
    ратиться в блоке except_suite.
    Если исключение возникнет во время выполнения блока try_suite, ин
    терпретатор поочередно проверит каждое предложение except. Если будет найдена соответствующая группа exception_group, будет выпол
    нен соответствующий блок except_suite. Соответствующей считается группа, в которой присутствует исключение того же типа, что и воз
    никшее исключение, или возникшее исключение является подклас
    сом
    1
    одного из исключений, перечисленных в группе.
    Например, если при поиске по словарю возникнет исключение KeyEr
    ror
    , первое предложение except, содержащее класс Exception, будет считаться соответствующим, так как KeyError является (косвенно) под
    классом Exception. Если ни одна из групп не содержит класс Exception
    (в чем нет ничего необычного), но имеется группа с классом Lookup
    1
    Как будет показано в главе 6, в объектноориентированном программиро
    вании обычно создаются иерархии классов, то есть один класс (тип дан
    ных) наследует другой класс. В языке Python родоначальником любой иерархии является класс object – все остальные классы прямо или косвен
    но наследуют его. Подкласс – это класс, который наследует другой класс,
    поэтому все классы в языке Python (за исключением класса object) являют
    ся подклассами, так как все они так или иначе наследуют класс object.

    194
    Глава 4. Управляющие структуры и функции
    Error
    , исключение KeyError будет соответствовать этой группе, потому что класс KeyError является подклассом класса LookupError. И если нет группы, в которой присутствовал бы класс Exception или LookupError,
    но имеется группа, содержащая класс KeyError, эта группа будет счи
    таться соответствующей. На рис. 4.1 приводится фрагмент иерархии классов исключений.
    Ниже приводится пример неправильного использования:
    try:
    x = d[5]
    except LookupError: # НЕВЕРНЫЙ ПОРЯДОК
    print("Lookup error occurred")
    except KeyError:
    print("Invalid key used")
    Если в словаре d не будет найден элемент с ключом 5, для нас было бы желательно обработать исключение KeyError, а не бо
    лее общее LookupError. Но в данном случае блок except с классом
    KeyError никогда не будет выполняться. В случае возникнове
    ния исключения KeyError соответствующим будет признан блок except с классом LookupError, потому что LookupError является базовым классом для KeyError, то есть класс LookupError нахо
    дится выше класса KeyError в иерархии классов исключений.
    Поэтому в случае использования нескольких блоков except не
    обходимо всегда располагать их сверху вниз в порядке от более специализированных (расположенных ниже в иерархии) к бо
    лее общим (расположенных выше в иерархии).
    try:
    x = d[k / n]
    except Exception: # ПЛОХАЯ ПРАКТИКА
    print("Something happened")
    object
    BaseException
    Exception
    ArithmeticError
    EnvironmentError
    EOFError
    LookupError
    ValueError
    IOError
    OSError
    IndexError
    KeyError
    Рис. 4.1. Фрагмент иерархии классов исключений в языке Python

    Обработка исключений
    195
    Обратите внимание, что обычно не принято указывать класс
    Exception в предложении except, так как оно будет соответство
    вать любому исключению и легко может скрыть логические ошибки в программном коде. Возможно, в этом примере пред
    полагалось перехватить исключение KeyError, но если n имеет значение 0, то мы неумышленно перехватим и исключение
    ZeroDivisionError
    Имеется также возможность записать предложение в форме except:
    , то есть вообще без указания группы исключений. По
    добный блок except будет перехватывать любые исключения,
    включая те, что наследуют класс BaseException, но не наследую
    щие класс Exception (они не показаны на рис. 4.1). Этот вариант порождает те же проблемы, что и при использовании предло
    жения except Exception, и даже еще хуже; такое предложение никогда не должно использоваться.
    Если интерпретатор Python не обнаружит ни одного соответствующего предложения except, он начнет подъем вверх по стеку вызовов, пыта
    ясь отыскать подходящий обработчик исключения. Если такой обра
    ботчик не будет найден, программа завершит свою работу с выводом диагностической информации и сообщения об ошибке.
    Если исключение не возникло, будет выполнен необязательный блок else
    , если таковой имеется. И в любом случае, то есть независимо от того, возникло ли исключение или нет, и было ли оно обработано или интерпретатору предстоит выполнить подъем по стеку вызовов, всегда
    выполняется блок finally, если он присутствует. Если исключение не возникло или было обработано одним из блоков except, блок finally бу
    дет выполнен самым последним, но если для возникшего исключения не было найдено соответствующего блока except, то сначала будет вы
    полнен блок finally, и только потом интерпретатор передаст исключе
    ние вверх по стеку вызовов. Такое гарантированное выполнение блока finally может быть очень полезным, когда необходимо обеспечить корректное освобождение ресурсов. На рис. 4.2 демонстрируется по
    рядок выполнения типичной конструкции try ... except ... finally.
    Ниже приводится окончательная версия функции list_find(), на этот раз она использует механизм обработки исключения:
    def list_find(lst, target):
    try:
    index = lst.index(target)
    except ValueError:
    index = 1
    return index
    Здесь мы использовали конструкцию try ... except для преобразова
    ния исключения в возвращаемое значение. Аналогичный подход мож
    но использовать для перехвата одних исключений и возбуждения дру
    гих – с этим приемом мы познакомимся очень скоро.

    196
    Глава 4. Управляющие структуры и функции
    Язык Python предоставляет возможность использовать более простую конструкцию try ... finally, которая в некоторых ситуациях может быть весьма удобна:
    try:
    try_suite
    finally:
    finally_suite
    Неважно, что произойдет в блоке try_suite (кроме краха системы или программы!), в любом случае блок finally_suite будет выполнен. Того же эффекта, аналогичного использованию конструкции try ... finally,
    можно достичь с помощью инструкции with и менеджера контекста
    (о которых будет рассказываться в главе 8).
    Очень часто конструкция try ... except ... finally используется для об
    работки ошибок, возникающих при работе с файлами. Например, про
    грамма noblanks.py принимает список имен файлов в виде аргументов командной строки и для каждого из них воспроизводит другой файл с тем же самым именем, но с расширением .nb, и с тем же содержи
    мым, за исключением пустых строк. Ниже приводится функция read_
    data()
    из этой программы:
    def read_data(filename):
    lines = []
    fh = None try:
    fh = open(filename, encoding="utf8")
    for line in fh:
    if line.strip():
    lines.append(line)
    try:
    # основные
    действия
    except exception:
    # обработка
    исключения
    finally:
    # освобождение
    ресурсов
    # выполнение
    продолжается
    здесь
    Необработанное исключение
    Обработанное исключение
    Нормальное выполнение
    # исключение
    передается вверх
    по стеку вызовов
    try:
    # основные
    действия
    except exception:
    # обработка
    исключения
    finally:
    # освобождение
    ресурсов
    # выполнение
    продолжается
    здесь
    try:
    # основные
    действия
    except exception:
    # обработка
    исключения
    finally:
    # освобождение
    ресурсов
    Рис. 4.2. Порядок выполнения конструкции try … except … finally

    Обработка исключений
    197
    except (IOError, OSError) as err:
    print(err)
    return []
    finally:
    if fh is not None:
    fh.close()
    return lines
    Изначально функция записывает в переменную fh значение None, так как вполне возможно, что вызов функции open() потерпит неудачу, то
    гда переменной fh ничего не будет присвоено (и в ней останется значе
    ние None) и будет возбуждено исключение. Если возникнет одно из ис
    ключений, которые мы определили (IOError или OSError), обработчик выведет сообщение об ошибке и вернет пустой список. Но, обратите внимание, что прежде, чем функция действительно вернет управле
    ние, будет выполнен блок finally и файл будет закрыт, если перед этим он был благополучно открыт.
    Обратите также внимание, что если возникнет ошибка, связанная с кодировкой символов, файл все равно будет закрыт, хотя функция не предусматривает обработку соответствующего исключения (Value
    Error
    ). Если это произойдет, интерпретатор сначала выполнит блок finally
    , а затем передаст исключение вверх по стеку вызовов, при этом возвращаемое значение будет отброшено, так как функция завершит
    ся в результате необработанного исключения. А так как в данном при
    мере нет соответствующего блока except, который обрабатывал бы ошибки, связанные с кодировкой, программа завершит свою работу с выводом диагностической информации.
    Предложение except в данном примере можно было бы записать более кратко:
    except EnvironmentError as err:
    print(err)
    return []
    Этот прием будет работать, так как EnvironmentError является базовым классом как для класса IOError, так и для класса OSError.
    В главе 8 демонстрируется более компактный способ, га
    рантирующий закрытие файлов, не требующий наличия блока finally.
    Возбуждение исключений
    Исключения представляют собой удобное средство управления пото
    ком выполнения. Мы можем воспользоваться этим, используя либо встроенные исключения, либо создавая свои собственные и возбуждая нужные нам, когда это необходимо. Возбудить исключение можно од
    ним из двух способов:
    Менеджеры контекста, стр. 428

    198
    Глава 4. Управляющие структуры и функции raise exception(args)
    raise
    В первом случае, то есть когда явно указывается возбуждаемое исклю
    чение, оно должно быть либо встроенным, либо нашим собственным,
    наследующим класс Exception. Если исключению в виде аргумента пе
    редается некоторый текст, этот текст будет выведен на экран, если ис
    ключение не будет обработано программой. Во втором случае, то есть когда исключение не указывается, инструкция raise повторно возбу
    дит текущее активное исключение, а в случае отсутствия активного исключения будет возбуждено исключение TypeError.
    Собственные исключения
    Собственные исключения – это наши собственные типы данных (клас
    сы). Создание классов будет рассматриваться в главе 6, но поскольку создать простейший тип собственного исключения не составляет ника
    кого труда, мы покажем, как это делается:
    class exceptionName(baseException): pass
    Базовым классом basException должен быть либо класс Exception, либо один из его наследников.
    Собственные исключения нередко используются, чтобы избежать глу
    боко вложенных циклов. Например, допустим, что у нас имеется объ
    ект table, хранящий записи (строки), каждая из которых состоит из полей (столбцов), в каждом из которых может иметься несколько зна
    чений (элементов). Тогда поиск определенного значения можно было бы реализовать примерно так:
    found = False for row, record in enumerate(table):
    for column, field in enumerate(record):
    for index, item in enumerate(field):
    if item == target:
    found = True break if found:
    break if found:
    break if found:
    print("found at ({0}, {1}, {2})".format(row, column, index))
    else:
    print("not found")
    Эти 15 строк программного кода осложняет тот факт, что нам при
    шлось предусмотреть прерывание каждого цикла в отдельности. Аль
    тернативное решение заключается в использовании нестандартного исключения:

    Обработка исключений
    199
    class FoundException(Exception): pass try:
    for row, record in enumerate(table):
    for column, field in enumerate(record):
    for index, item in enumerate(field):
    if item == target:
    raise FoundException()
    except FoundException:
    print("found at ({0}, {1}, {2})".format(row, column, index))
    else:
    print("not found")
    Этот прием позволил сократить программный код до десяти строк (или до 11, если включить определение класса исключения) и придал коду более удобочитаемый вид. Если искомый элемент будет найден, возбу
    ждается наше собственное исключение и выполняется соответствую
    щий блок except, при этом блок else не выполняется. Если искомый элемент не будет найден, исключение не возбуждается и тогда в конце выполняется блок else.
    Рассмотрим еще один пример, демонстрирующий другие способы об
    работки исключений. Все фрагменты взяты из программы check
    tags.py
    , которая читает содержимое файлов HTML, имена которых пе
    редаются в виде аргументов командной строки, и выполняет некото
    рые простые проверки, чтобы убедиться, что все теги начинаются с символа «<» и заканчиваются символом «>» и все сущности оформле
    ны правильно. В программе определяются четыре нестандартных ис
    ключения:
    class InvalidEntityError(Exception): pass class InvalidNumericEntityError(invalidEntityError): pass class InvalidAlphaEntityError(invalidEntityError): pass class InvalidTagContentError(Exception): pass
    Второе и третье исключения наследуют первое; для чего это необходи
    мо, мы увидим, когда будем обсуждать программный код, использую
    щий эти исключение. Функция parse(), использующая эти исключе
    ния, содержит более 70 строк программного кода, поэтому мы пока
    жем только ту часть функции, которая имеет непосредственное отно
    шение к обработке исключений.
    fh = None try:
    fh = open(filename, encoding="utf8")
    errors = False for lino, line in enumerate(fh, start=1):
    for column, c in enumerate(line, start=1):
    try:
    Этот фрагмент начинается вполне традиционно, записывая значение
    None в переменную, которая впоследствии будет ссылаться на объект

    200
    Глава 4. Управляющие структуры и функции файла, и помещая все действия с файлом в блок try. Программа читает содержимое файла строку за строкой и каждую строку символ за сим
    волом.
    Примечательно, что здесь имеется два блока try – внешний использу
    ется для обработки исключений, которые могут возникнуть при рабо
    те с объектом файла, а внутренний – для обработки исключений, воз
    никающих в ходе синтаксического анализа.
    elif state == PARSING_ENTITY:
    if c == ";":
    if entity.startswith("#"):
    if frozenset(entity[1:])  HEXDIGITS:
    raise InvalidNumericEntityError()
    elif not entity.isalpha():
    raise InvalidAlphaEntityError()
    Функция может находиться в нескольких состояниях, например, по
    сле чтения символа амперсанда (&) она входит в состояние PARSING_EN
    TITY
    и запоминает символы, расположенные между амперсандом и точ
    кой с запятой (но не включая их), в строке entity.
    Часть программного кода, которая показана здесь, обра
    батывает случай, когда точка с запятой была обнаруже
    на в процессе чтения сущности. Если это числовая сущ
    ность (начинается комбинацией символов «&#», за кото
    рой следуют шестнадцатеричные цифры и символ «;»,
    например: «AC;»), мы преобразуем числовую часть в множество и исключаем из него все шестнадцатерич
    ные цифры – если после этого множество окажется не
    пустым, следовательно, в числе был указан как мини
    мум один ошибочный символ, и мы возбуждаем собст
    венное исключение. Если это текстовая сущность (ком
    бинация, начинающаяся с символа «&», за которым сле
    дуют алфавитные символы и символ «;», например:
    «©»), мы возбуждаем исключение, если будет обна
    ружен неалфавитный символ.
    except (invalidEntityError,
    InvalidTagContentError) as err:
    if isinstance(err, InvalidNumericEntityError):
    error = "invalid numeric entity"
    elif isinstance(err, InvalidAlphaEntityError):
    error = "invalid alphabetic entity"
    elif isinstance(err, InvalidTagContentError):
    error = "invalid tag"
    print("ERROR {0} in {1} on line {2} column {3}"
    Тип
    set
    , стр. 144

    Обработка исключений
    201
    .format(error, filename, lino, column))
    if skip_on_first_error:
    raise
    Если возникает исключение, связанное с синтаксиче
    ским анализом, оно будет перехвачено блоком except.
    Используя базовый класс InvalidEntityError, мы пере
    хватим оба типа исключений – InvalidNumericEntityError и InvalidAlphaEntityError. После этого с помощью функ
    ции isinstance() проверяется, какое именно исключение возникло, и определяется соответствующее сообщение об ошибке. Встроенная функция isinstance() возвращает
    True
    , если первый ее аргумент имеет тот же тип, что и тип (или один из его базовых типов), переданный во втором аргументе.
    Можно было бы использовать отдельные блоки except для каждого из трех наших собственных исключений синтаксического анализа, но в данном случае, объединив обработку в одном блоке, нам удалось из
    бежать необходимости повторять четыре последние строки (от инст
    рукции print() до инструкции raise) в каждом из них.
    Программа имеет два режима работы. Если переменная skip_on_first_
    error имеет значение False, программа продолжит проверку файла да
    же после обнаружения синтаксической ошибки, что может привести к выводу множества сообщений об ошибках для каждого файла. Если переменная skip_on_first_error имеет значение True, то после выявле
    ния синтаксической ошибки (одной и только одной) в файле програм
    ма выведет сообщение об ошибке и повторно возбудит исключение синтаксического анализа, которое будет перехвачено внешним блоком try
    (где выполняется обработка каждого файла).
    elif state == PARSING_ENTITY:
    raise EOFError("missing ';' at end of " + filename)
    По завершении синтаксического анализа нам необходимо проверить,
    не оказались ли мы в середине сущности. Если это произошло, возбуж
    дается встроенное исключение EOFError, сообщающее о встрече конца файла, которому мы передаем собственный текст сообщения. Точно так же для этой цели мы могли бы использовать свое собственное ис
    ключение.
    except (invalidEntityError, InvalidTagContentError):
    pass # Уже было обработано except EOFError as err:
    print("ERROR unexpected EOF:", err)
    except EnvironmentError as err:
    print(err)
    Функция
    1   ...   20   21   22   23   24   25   26   27   ...   74


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