Исключения
==========

Исключения возникают тогда, когда в программе возникает некоторая 
*исключительная* ситуация. Например, к чему приведёт попытка чтения 
несуществующего файла? Или если файл был случайно удалён, пока программа 
работала? Такие ситуации обрабатываются при помощи **исключений**.

Это касается и программ, содержащих недействительные команды. В этом
случае Python **поднимает** руки и сообщает, что обнаружил **ошибку**.


Ошибки
------

Рассмотрим простой вызов функции ``print``. Что, если мы ошибочно напишем
``print`` как ``Print``? Обратите внимание на заглавную букву. В этом случае
Python *поднимает* синтаксическую ошибку.

.. sourcecode:: python

    >>> Print('Привет, Мир!')
    Traceback (most recent call last):
      File "<pyshell#0>", line 1, in <module>
        Print('Привет, Мир!')
    NameError: name 'Print' is not defined
    >>> print('Привет, Мир!')
    Привет, Мир!

Обратите внимание, что была поднята ошибка ``NameError``, а также указано место,
где была обнаружена ошибка. Так в данном случае действует *обработчик ошибок*.


Исключения
----------


**Попытаемся** считать что-либо от пользователя. Нажмите ``Сtrl-D`` (или 
``Ctrl+Z`` в Windows) и посмотрите, что произойдёт.

.. sourcecode:: python

    >>> s = input('Введите что-нибудь --> ')
    Введите что-нибудь --> 
    Traceback (most recent call last):
      File "<pyshell#2>", line 1, in <module>
        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 -- *англ.* "с" (*прим.перев.*)