Python3 Ошибки и исключения

Как новичок в Python, когда вы только начинаете изучать программирование на Python, вы часто видите сообщения об ошибках. Раньше мы их не упоминали; эта глава специально познакомит с ними.

В Python есть два типа ошибок, которые легко распознать: синтаксические ошибки и исключения.

Python assert (утверждение) используется для вычисления выражения и вызывает исключение, когда условие выражения ложно.

Синтаксические ошибки

Синтаксические ошибки Python, также называемые ошибками разбора, часто встречаются у начинающих, как в следующем примере:

>>> while True print('Hello world')
  File "<stdin>", line 1, in ?
    while True print('Hello world')
                   ^
SyntaxError: неверный синтаксис

В этом примере в функции print() обнаружена ошибка: перед ней отсутствует двоеточие.: 。

Синтаксический анализатор указывает строку с ошибкой и отмечает маленькой стрелкой позицию, где ошибка была впервые обнаружена.

Исключения

Даже если синтаксис программы на Python корректен, при её запуске всё ещё могут возникать ошибки. Ошибки, обнаруженные во время выполнения, называются исключениями.

Большинство исключений не обрабатываются программой и появляются здесь в виде сообщений об ошибках:

Пример

>>> 10 * (1/0)             # 0 нельзя использовать в качестве делителя, вызывает исключение
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: деление на ноль
>>> 4 + spam*3             # spam не определён, вызывает исключение
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: имя 'spam' не определено defined
>>> '2' + 2               # int нельзя добавить к str, вызывает исключение
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: можно только конкатенировать str (не "int") с str

Исключения бывают разных типов, и все эти типы выводятся как часть информации: типы в примере — ZeroDivisionError, NameError и TypeError.

Начальная часть сообщения об ошибке показывает контекст, в котором произошло исключение, и отображает конкретную информацию в виде стека вызовов.

Обработка исключений

try/except

Исключения можно перехватывать с помощьюtry/exceptинструкции.

В следующем примере пользователя просят ввести допустимое целое число, но пользователю разрешено прервать программу (с помощью Control-C или метода, предоставляемого операционной системой). Прерывание пользователем вызовет исключение KeyboardInterrupt.

while True:
    try:
        x = int(input("Пожалуйста, введите число: "))
        break
    except ValueError:
        print("Вы не ввели число, пожалуйста, попробуйте снова!")

Инструкция try работает следующим образом:

  • Сначала выполняется блок try (инструкции между ключевым словом try и ключевым словом except).

  • Если исключение не возникает, блок except игнорируется, и блок try завершается после выполнения.

  • Если во время выполнения блока try возникает исключение, остальная часть блока try игнорируется. Если тип исключения совпадает с именем после except, то будет выполнен соответствующий блок except.

  • Если исключение не совпадает ни с одним except, то это исключение будет передано во внешний try.

Инструкция try может содержать несколько блоков except для обработки различных конкретных исключений соответственно. Будет выполнена не более чем одна ветвь.

Обработчик будет обрабатывать исключения только в соответствующем блоке try, а не исключения в обработчиках других инструкций try.

Блок except может обрабатывать несколько исключений одновременно; эти исключения заключаются в круглые скобки, образуя кортеж, например:

except (RuntimeError, TypeError, NameError):
    pass

Последняя ветвь except может опускать имя исключения, и она будет использоваться как подстановочный знак. Вы можете использовать этот метод, чтобы вывести сообщение об ошибке и затем снова возбудить исключение.

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("Ошибка ОС: {0}".format(err))
except ValueError:
    print("Не удалось преобразовать данные в целое число.")
except:
    print("Непредвиденная ошибка:", sys.exc_info()[0])
    raise

try/except...else

try/exceptУ инструкции также есть необязательнаяelseветвь. Если эта ветвь используется, она должна быть размещена после всех ветвей except.

Ветвь else будет выполнена, когда в ветви try не возникнет исключение.

В следующем примере в инструкции try проверяется, можно ли открыть файл. Если файл открывается нормально без исключения, выполняется часть else для чтения содержимого файла:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

Использование ветви else лучше, чем помещение всех операторов в блок try, потому что это позволяет избежать некоторых неожиданных исключений, которые except не может перехватить.

Обработка исключений не только обрабатывает исключения, возникающие непосредственно в блоке try, но и исключения, возбуждаемые в функциях, вызываемых в этом блоке (даже в косвенно вызываемых функциях). Например:

>>> def this_fails():
        x = 1/0
   
>>> try:
        this_fails()
    except ZeroDivisionError as err:
        print('Handling run-time error:', err)
   
Handling run-time error: int division or остаток от деления на ноль

Оператор try-finally

Оператор try-finally выполняет завершающий код независимо от того, возникло ли исключение.

В следующем примере оператор finally выполняется независимо от того, возникает ли исключение:

Пример

try:
    пример()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)
finally:
    print('Это предложение будет выполнено независимо от того, возникнет ли исключение.')

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

Python использует оператор raise для возбуждения указанного исключения.

Формат синтаксиса raise следующий:

raise [Exception [, args [, traceback]]]

В следующем примере возбуждается исключение, если x больше 5:

x = 10
if x > 5:
    raise Exception('x не может быть больше 5. Значение x: {}'.format(x))

Выполнение приведенного выше кода вызовет исключение:

Traceback (most recent call last):
  File "test.py", line 3, in <module>
    raise Exception('x 不能大于 5。x 的值为: {}'.format(x))
Exception: x 不能大于 5。x 的值为: 10

Единственный параметр raise указывает исключение, которое нужно возбудить. Он должен быть экземпляром исключения или классом исключения (то есть подклассом Exception).

Если вы хотите только узнать, было ли возбуждено исключение, и не хотите его обрабатывать, то простой оператор raise может возбудить его снова.

>>> try:
        raise NameError('HiThere')  # Имитировать исключение.
    except NameError:
        print('Исключение пролетело!')
        raise
   
Исключение пролетело!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

Пользовательские исключения

Вы можете создать собственные исключения, создав новый класс исключения. Класс исключения наследуется от класса Exception, напрямую или косвенно, например:

>>> class MyError(Exception):
        def __init__(self, value):
            self.value = value
        def __str__(self):
            return repr(self.value)
   
>>> try:
        raise MyError(2*2)
    except MyError as e:
        print('Произошло моё исключение, значение:', e.value)
   
Произошло моё исключение, значение: 4
>>> raise MyError('упс!')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'упс!'

В этом примере переопределяется стандартный __init__() класса Exception.

Когда модуль может возбуждать несколько различных исключений, обычной практикой является создание базового класса исключений для этого пакета, а затем создание различных подклассов для разных ошибочных ситуаций на основе этого базового класса:

class Error(Exception):
    """Базовый класс для исключений в этом модуле."""
    pass

class InputError(Error):
    """Исключение, возбуждаемое при ошибках во входных данных.

    Атрибуты:
        expression -- входное выражение, в котором произошла ошибка
        message -- объяснение ошибки
    """


    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Возникает, когда операция пытается выполнить переход состояния, который не
    разрешён.

    Атрибуты:
        previous -- состояние в начале перехода
        next -- новое состояние, к которому пытаются перейти
        message -- объяснение того, почему конкретный переход не разрешён
    """


    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

Большинство имён исключений заканчиваются на 'Error', как и стандартные имена исключений.


Определение завершающих действий

Инструкция try имеет еще один необязательный блок, который определяет завершающие действия, выполняемые при любых обстоятельствах. Например:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
KeyboardInterrupt

В приведенном выше примере, независимо от того, возникает ли исключение в блоке try, блок finally будет выполнен.

Если в блоке try (или в блоках except и else) возникает исключение, и ни один except его не перехватывает, то это исключение будет возбуждено после выполнения блока finally.

Ниже приведен более сложный пример (содержащий как блок except, так и блок finally в одной инструкции try):

>>> def divide(x, y):
        try:
            result = x / y
        except ZeroDivisionError:
            print("division by zero!")
        else:
            print("результат равен", result)
        finally:
            print("выполняется блок finally")
   
>>> divide(2, 1)
result is 2.0
выполняется finally блок
>>> divide(2, 0)
деление на ноль!
выполняется finally блок
>>> divide("2", "1")
выполняется finally блок
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  Файл "<stdin>", строка 3, в divide
TypeError: неподдерживаемый тип операнда(s) для /: 'str' и 'str'

Предопределенные действия по очистке

Некоторые объекты определяют стандартное поведение очистки. Независимо от того, успешно ли система его использовала, как только оно больше не понадобится, это стандартное поведение очистки будет выполнено.

Следующий пример демонстрирует попытку открыть файл и затем вывести его содержимое на экран:

for line in open("myfile.txt"):
    print(line, end="")

Проблема с приведённым выше кодом заключается в том, что после выполнения файл остаётся открытым и не закрывается.

Оператор с ключевым словом with может гарантировать, что такие объекты, как файлы, правильно выполнят свой метод очистки после использования:

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

После выполнения приведённого выше кода, даже если во время обработки возникает проблема, файл f всегда будет закрыт.

Для получения дополнительной информации о ключевом слове with см.:Python: ключевое слово with


Связанные материалы

Python assert (утверждение)

Ключевое слово with в Python

Другие расширения