Исключения ========== Исключения возникают тогда, когда в программе возникает некоторая *исключительная* ситуация. Например, к чему приведёт попытка чтения несуществующего файла? Или если файл был случайно удалён, пока программа работала? Такие ситуации обрабатываются при помощи **исключений**. Это касается и программ, содержащих недействительные команды. В этом случае Python **поднимает** руки и сообщает, что обнаружил **ошибку**. Ошибки ------ Рассмотрим простой вызов функции ``print``. Что, если мы ошибочно напишем ``print`` как ``Print``? Обратите внимание на заглавную букву. В этом случае Python *поднимает* синтаксическую ошибку. .. sourcecode:: python >>> Print('Привет, Мир!') Traceback (most recent call last): File "", line 1, in Print('Привет, Мир!') NameError: name 'Print' is not defined >>> print('Привет, Мир!') Привет, Мир! Обратите внимание, что была поднята ошибка ``NameError``, а также указано место, где была обнаружена ошибка. Так в данном случае действует *обработчик ошибок*. Исключения ---------- **Попытаемся** считать что-либо от пользователя. Нажмите ``Сtrl-D`` (или ``Ctrl+Z`` в Windows) и посмотрите, что произойдёт. .. sourcecode:: python >>> s = input('Введите что-нибудь --> ') Введите что-нибудь --> Traceback (most recent call last): File "", line 1, in s = input('Введите что-нибудь --> ') EOFError: EOF when reading a line Python поднимает ошибку с именем ``EOFError``, что означает, что он обнаружил символ *конца файла* (который вводится при помощи ``Ctrl-D``) там, где не ожидал. Обработка исключений -------------------- Обрабатывать исключения можно при помощи оператора ``try..except``\ [1]_. При этом все обычные команды помещаются внутрь try-блока, а все обработчики исключений -- в except-блок. **Пример:** (сохраните как ``try_except.py``) .. sourcecode:: python try: text = input('Введите что-нибудь --> ') except EOFError: print('Ну зачем вы сделали мне EOF?') except KeyboardInterrupt: print('Вы отменили операцию.') else: print('Вы ввели {0}'.format(text)) **Вывод:** :: $ python3 try_except.py Введите что-нибудь --> # Нажмите ctrl-d Ну зачем вы сделали мне EOF? $ python3 try_except.py Введите что-нибудь --> # Нажмите ctrl-c Вы отменили операцию. $ python3 try_except.py Введите что-нибудь --> без ошибок Вы ввели без ошибок **Как это работает:** Здесь мы поместили все команды, которые могут вызвать исключения/ошибки, внутрь блока ``try``, а затем поместили обработчики соответствующих ошибок/исключений в блок ``except``. Выражение ``except`` может обрабатывать как одиночную ошибку или исключение, так и список ошибок/исключений в скобках. Если не указано имя ошибки или исключения, обрабатываться будут *все* ошибки и исключения. Помните, что для каждого выражения ``try`` должно быть хотя бы одно соответствующее выражение ``except``. Иначе какой смысл был бы в блоке ``try``? Если ошибка или исключение не обработано, будет вызван обработчик Python по умолчанию, который останавливает выполнение программы и выводит на экран сообщение об ошибке. Выше мы уже видели это в действии. Можно также добавить пункт ``else`` к соответствующему блоку ``try..except``. Этот пункт будет выполнен тогда, когда исключений не возникает. В следующем примере мы увидим, как можно получить объект исключения для дальнейшей работы с ним. Вызов исключения ---------------- Исключение можно *поднять* при помощи оператора ``raise``\ [2]_, передав ему имя ошибки/исключения, а также объект исключения, который нужно *выбросить*. Вызываемая ошибка или исключение должна быть классом, который прямо или непрямо является производным от класса ``Exception``. **Пример:** (сохраните как ``raising.py``) .. sourcecode:: python class ShortInputException(Exception): '''Пользовательский класс исключения.''' def __init__(self, length, atleast): Exception.__init__(self) self.length = length self.atleast = atleast try: text = input('Введите что-нибудь --> ') if len(text) < 3: raise ShortInputException(len(text), 3) # Здесь может происходить обычная работа except EOFError: print('Ну зачем вы сделали мне EOF?') except ShortInputException as ex: print('ShortInputException: Длина введённой строки -- {0}; \ ожидалось, как минимум, {1}'.format(ex.length, ex.atleast)) else: print('Не было исключений.') **Вывод:** :: $ python3 raising.py Введите что-нибудь --> а ShortInputException: Длина введённой строки -- 1; ожидалось, как минимум, 3 $ python3 raising.py Введите что-нибудь --> абв Не было исключений. **Как это работает:** Здесь мы создаём наш собственный тип исключения. Этот новый тип исключения называется ``ShortInputException``. Он содержит два поля: ``length``, хранящее длину введённого текста, и ``atleast``, указывающее, какую минимальную длину текста ожидала программа. В пункте ``except`` мы указываем класс ошибки ``ShortInputException``, который будет сохранён как\ [3]_ переменная ``ex``, содержащая соответствующий объект ошибки/исключения. Это аналогично параметрам и аргументам при вызове функции. Внутри этого пункта ``except`` мы используем поля ``length`` и ``atleast`` объекта исключения для вывода необходимых сообщений пользователю. Try .. Finally -------------- Представим, что в программе происходит чтение файла и необходимо убедиться, что объект файла был корректно закрыт и что не возникло никакого исключения. Этого можно достичь с применением блока ``finally``. Сохраните как ``finally.py``: .. sourcecode:: python import time try: f = open('poem.txt') while True: # наш обычный способ читать файлы line = f.readline() if len(line) == 0: break print(line, end='') time.sleep(2) # Пусть подождёт некоторое время except KeyboardInterrupt: print('!! Вы отменили чтение файла.') finally: f.close() print('(Очистка: Закрытие файла)') **Вывод:** :: $ python3 finally.py Программировать весело Если работа скучна, Чтобы придать ей весёлый тон - !! Вы отменили чтение файла. (Очистка: Закрытие файла) **Как это работает:** Здесь мы производим обычные операции чтения из файла, но в данном случае добавляем двухсекундный сон после вывода каждой строки при помощи функции ``time.sleep``, чтобы программа выполнялась медленно (ведь Python очень быстр от природы). Во время выполнения программы нажмите ``ctrl-c``, чтобы прервать/отменить выполнение программы. Пронаблюдайте, как при этом выдаётся исключение ``KeyboardInterrupt``, и программа выходит. Однако, прежде чем программа выйдет, выполняется пункт ``finally``, и файловый объект будет всегда закрыт. Оператор with ------------- Типичной схемой является запрос некоторого ресурса в блоке ``try`` с последующим освобождением этого ресурса в блоке ``finally``. Для того, чтобы сделать это более "чисто", существует оператор ``with``\ [4]_: Сохраните как ``using_with.py``: .. sourcecode:: python with open("poem.txt") as f: for line in f: print(line, end='') **Как это работает:** Вывод должен быть таким же, как и в предыдущем примере. Разница лишь в том, что здесь мы используем функцию ``open`` с оператором ``with`` -- этим мы оставляем автоматическое закрытие файла под ответственность ``with open``. За кулисами происходит следующее. Существует некий протокол, используемый оператором ``with``. Он считывает объект, возвращаемый оператором ``open``. Назовём его в данном случае "thefile". Перед запуском блока кода, содержащегося в нём, оператор ``with`` *всегда* вызывает функцию ``thefile.__enter__``, а также *всегда* вызывает ``thefile.__exit__`` после завершения выполнения этого блока кода. Так что код, который мы бы написали в блоке ``finally``, будет автоматически обработан методом ``__exit__``. Это избавляет нас от необходимости повторно в явном виде указывать операторы ``try..finally``. Более обширное рассмотрение этой темы выходит за рамки настоящей книги, поэтому для более исчерпывающего объяснения см. :pep:`343`. Резюме ------ Мы обсудили использование операторов ``try..except`` и ``try..finally``. Мы также увидели, как создавать наши собственные типы исключений и как их вызывать. Далее мы ознакомимся со стандартной библиотекой Python. Примечания ---------- .. [1] try -- *англ.* "пытаться" (*прим.перев.*) .. [2] raise -- *англ.* "поднимать" (*прим.перев.*) .. [3] as -- *англ.* "как" (*прим.перев.*) .. [4] with -- *англ.* "с" (*прим.перев.*)