Встроенные исключения Python: пошаговое руководство с примерами
Python имеет полный набор встроенных исключений, которые обеспечивают быстрый и эффективный способ обработки ошибок и исключительных ситуаций, которые могут возникнуть в вашем коде. Знание наиболее часто используемых встроенных исключений является ключевым моментом для вас как разработчика Python. Эти знания помогут вам отлаживать код, поскольку каждое исключение имеет особое значение, которое может пролить свет на ваш процесс отладки.
Вы также сможете обрабатывать и вызывать большинство встроенных исключений в вашем коде Python, что является отличным способом справиться с ошибками и исключительными ситуациями без необходимости создавать свои собственные исключения.
В этом уроке вы:
- Узнайте, что такое ошибки и исключения в Python.
- Поймите, как Python организует встроенные исключения в иерархию классов.
- Изучите наиболее часто используемые встроенные исключения.
- Узнайте, как обрабатывать и вызывать встроенные исключения в вашем коде.
Чтобы плавно пройти это руководство, вы должны быть знакомы с некоторыми основными концепциями Python. Эти концепции включают классы Python, иерархии классов, исключения, блоки try
… кроме
и оператор raise
.
Ошибки и исключения в Python
Ошибки и исключения — важные понятия в программировании, и вы, вероятно, потратите значительное количество времени на их устранение в своей карьере программиста. Ошибки – это конкретные условия, такие как синтаксические и логические ошибки, из-за которых ваш код работает некорректно или даже приводит к сбою.
Зачастую исправить ошибки можно, обновив или изменив код, установив новую версию зависимости, проверив логику кода и так далее.
Например, предположим, что вам нужно убедиться, что данная строка содержит определенное количество символов. В этом случае вы можете использовать встроенную функцию len()
:
>>> len("Pythonista") = 10
File "<input>", line 1
...
SyntaxError: cannot assign to function call here.
Maybe you meant '==' instead of '='?
В этом примере вы используете неправильный оператор. Вместо использования оператора сравнения на равенство вы используете оператор присваивания. Этот код вызывает ошибку SyntaxError
, которая представляет собой синтаксическую ошибку, как следует из ее названия.
Примечание. В приведенном выше коде вы заметите, насколько хорошо сообщение об ошибке предлагает возможное решение для исправления кода. Начиная с версии 3.10 разработчики ядра Python приложили немало усилий для улучшения сообщений об ошибках, чтобы сделать их более дружелюбными и полезными для отладки.
Чтобы исправить ошибку, необходимо локализовать затронутый код и исправить синтаксис. Это действие устранит ошибку:
>>> len("Pythonista") == 10
True
Теперь код работает правильно, и ошибка SyntaxError
исчезла. Таким образом, ваш код не сломается, и ваша программа продолжит нормальное выполнение.
Из приведенного выше примера есть чему поучиться. Вы можете исправить ошибки, но не можете обработать их. Другими словами, если у вас есть синтаксическая ошибка, подобная той, что показана в примере, вы не сможете обработать эту ошибку и запустить код. Вам нужно исправить синтаксис.
С другой стороны, исключения — это события, которые прерывают выполнение программы. Как следует из названия, исключения возникают в исключительных ситуациях, которые должны или не должны происходить. Итак, чтобы предотвратить сбой вашей программы после исключения, вы должны обрабатывать это исключение с помощью соответствующего механизма обработки исключений.
Чтобы лучше понять исключения, предположим, что у вас есть выражение Python, например a + b
. Это выражение будет работать, если a
и b
являются строками или числами:
>>> a = 4
>>> b = 3
>>> a + b
7
В этом примере код работает правильно, поскольку a
и b
являются числами. Однако выражение вызывает исключение, если a
и b
относятся к типам, которые нельзя сложить вместе:
>>> a = "4"
>>> b = 3
>>> a + b
Traceback (most recent call last):
File "<input>", line 1, in <module>
a + b
~~^~~
TypeError: can only concatenate str (not "int") to str
Поскольку a
— это строка, а b
— число, ваш код завершается с ошибкой TypeError
. Поскольку нет возможности добавлять текст и цифры, ваш код столкнулся с исключительной ситуацией.
Python использует классы для представления исключений и ошибок. Эти классы обычно называются исключениями, независимо от того, что представляет собой конкретный класс, исключение или ошибку. Классы исключений предоставляют нам информацию о исключительной ситуации, а также о ошибках, обнаруженных во время выполнения программы.
Первый пример в этом разделе демонстрирует синтаксическую ошибку в действии. Класс SyntaxError
представляет ошибку, но реализован как исключение Python. Это может сбивать с толку, но Python использует классы исключений как для ошибок, так и для исключений.
Другим примером исключения может быть ситуация, когда вы работаете над фрагментом кода, обрабатывающим текстовый файл, а этот файл не существует. В этом случае в вашем коде нет ошибки. У вас исключительная ситуация, которую вам необходимо обработать, чтобы предотвратить сбой программы. Вы не можете контролировать проблему, поскольку не можете гарантировать существование файла, изменив свой код. Вам нужно обработать исключение.
Вы можете использовать блоки try
… кроме
для обработки исключений в Python. В следующем разделе вы узнаете основы того, как это делать.
Обработка исключений
Если у вас есть фрагмент кода, который вызывает исключение, и вы не предоставляете код обработчика этого исключения, ваша программа перестанет работать. После этого на стандартном выводе (на вашем экране) появится трассировка исключения.
Примечание. Чтобы изучить основы обработки исключений в Python, ознакомьтесь с руководством «Исключения Python: введение».
В Python вы можете обрабатывать исключения с помощью оператора try
… кроме
, который позволяет перехватывать исключение и выполнять восстановительные действия.
Рассмотрим следующий пример. Распространенным исключением, которое вы увидите, когда начнете использовать списки и кортежи Python, является IndexError
. Это исключение возникает, когда вы пытаетесь получить доступ к индексу, который находится за пределами диапазона:
>>> numbers = [1, 2, 3]
>>> numbers[5]
Traceback (most recent call last):
...
IndexError: list index out of range
В этом примере список чисел имеет только три значения. Итак, когда вы пытаетесь получить доступ к индексу 5
в операции индексирования, вы получаете ошибку IndexError
, которая нарушает код. Вы можете обернуть этот код в блок try
… кроме
, чтобы предотвратить поломку:
>>> try:
... numbers[5]
... except IndexError:
... print("Your list doesn't have that index 😔")
...
Your list doesn't have that index 😔
Теперь код не прерывается с исключением. Вместо этого он выводит сообщение на экран. Обратите внимание, что вызов print()
— это всего лишь действие-заполнитель для примера. В реальном коде здесь вы можете делать и другие вещи.
Приведенный выше пример иллюстрирует самую базовую конструкцию для обработки исключений в Python. Вы можете ознакомиться с предложенным выше руководством, чтобы глубже погрузиться в обработку исключений. Теперь пришло время узнать об обратной стороне медали. Вы также можете создавать исключения в Python.
Вызов исключений
В состав синтаксиса Python входит оператор raise
. Вы можете использовать этот оператор для вызова исключений в вашем коде в ответ на исключительные ситуации.
Примечание. Чтобы глубже погрузиться в создание исключений в Python, ознакомьтесь с учебным пособием «Поднятие Python: эффективное создание исключений в вашем коде».
В качестве примера использования оператора raise
предположим, что вам нужно написать функцию для расчета средней оценки учащихся. У вас получится следующая функция:
>>> def average_grade(grades):
... return sum(grades) / len(grades)
...
>>> average_grade([5, 4, 5, 3])
4.25
>>> average_grade([])
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
Эта функция работает нормально. Однако если список оценок пуст, вы получите ошибку деления на ноль, поскольку len(grades)
будет 0
. Когда пользователь кода увидит сообщение об ошибке, он может запутаться. Ошибка деления нуля? Что является причиной этого?
Лучшим подходом, вероятно, было бы убедиться, что входной список не пуст, и в этом случае вызвать более подходящее исключение:
>>> def average_grade(grades):
... if not grades:
... raise ValueError("empty grades not allowed")
... return sum(grades) / len(grades)
...
>>> average_grade([5, 4, 5, 3])
4.25
>>> average_grade([])
Traceback (most recent call last):
...
ValueError: empty grades not allowed
В этой обновленной версии average_grade()
вы добавляете условный оператор, который проверяет, пусты ли входные данные. Если это так, вы вызываете ValueError
с явным сообщением об ошибке, которое четко сообщает, что не так с кодом.
Исключения IndexError
и ValueError
являются примерами часто используемых встроенных исключений в Python. В следующих разделах вы узнаете больше об этих и некоторых других встроенных исключениях.
Эти знания помогут вам во многих отношениях. Во-первых, вы сможете быстро определить тип ошибки, которая может возникнуть в вашем коде, что улучшит ваши навыки отладки. Во-вторых, вы будете вооружены широким арсеналом уже доступных исключений, которые можно будет использовать в собственном коде, освобождая себя от необходимости создавать собственные исключения.
Встроенные исключения Python
Python имеет более шестидесяти встроенных исключений, которые представляют собой широкий спектр распространенных ошибок и исключительных ситуаций. Эти исключения разделены на две группы:
- Исключения базового класса
- Конкретные исключения
Первая группа исключений включает классы исключений, которые в основном используются в качестве базовых классов для других исключений. Например, в этой группе есть класс Exception
, который специально разработан для того, чтобы вы могли создавать собственные исключения.
Вторая группа содержит исключения, которые вы обычно видите в коде Python или получаете при выполнении кода Python. Например, вы, вероятно, встречали некоторые из следующих конкретных исключений в своем повседневном программировании:
ImportError
Появляется, когда оператор
import
не может загрузить модуль.ModuleNotFoundError
Происходит, когда
import
не может найти данный модуль.NameError
Появляется, когда имя не определено в глобальной или локальной области.
AttributeError
Происходит в случае сбоя ссылки или назначения атрибута.
IndexError
Происходит, когда операция индексирования последовательности использует индекс, выходящий за пределы диапазона.
KeyError
Происходит, когда ключ отсутствует в словаре или другом сопоставлении.
ZeroDivisionError
Появляется, когда второй операнд в операции деления или по модулю равен
0
TypeError
Происходит, когда операция, функция или метод воздействует на объект неподходящего типа.
ValueError
Происходит, когда операция, функция или метод получает правильный тип аргумента, но неправильное значение.
Эта таблица представляет собой лишь небольшой пример встроенных исключений Python. Полный список всех встроенных исключений можно найти на странице «Встроенные исключения» документации Python.
Взглянем на иерархию исключений
Как вы уже знаете, в Python имеется множество встроенных исключений. Вы можете изучить их, проверив пространство имен builtins
в сеансе REPL:
>>> import builtins
>>> dir(builtins)
[
'ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
...
]
В этом примере вы сначала импортируете пространство имен builtins
. Затем вы используете встроенную функцию dir()
для получения списка имен, которые определяет этот модуль. Обратите внимание, что вы получите полный список встроенных имен. Между ними вы найдете встроенные исключения.
Встроенные исключения Python закодированы как классы и организованы в иерархию классов, которая включает следующие уровни:
- Базовые классы. Они предоставляют базовые классы для других исключений. Вы должны использовать их только как родительские классы. Однако некоторые из этих исключений, например класс
Exception
, можно встретить в некоторых базах кода. - Конкретные исключения. Это исключения, которые Python генерирует в ответ на различные исключительные ситуации. Они также предоставляют отличную базу конкретных исключений, которые вы можете при необходимости вызвать в своем собственном коде.
- Исключения ОС. Они предоставляют исключения, генерируемые операционной системой. Python передает их в ваше приложение. В большинстве случаев вы будете перехватывать эти исключения, но не вызывать их в своем коде.
- Предупреждения. Они предупреждают о непредвиденных событиях или действиях, которые в дальнейшем могут привести к ошибкам. Эти конкретные типы исключений не представляют собой ошибки. Игнорирование их может вызвать проблемы позже, но вы можете их игнорировать.
Схема иерархии исключений приведена ниже:
BaseException
├── BaseExceptionGroup
├── GeneratorExit
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ExceptionGroup [BaseExceptionGroup]
├── ImportError
│ └── ModuleNotFoundError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
│ └── UnboundLocalError
├── OSError
│ ├── BlockingIOError
│ ├── ChildProcessError
│ ├── ConnectionError
│ │ ├── BrokenPipeError
│ │ ├── ConnectionAbortedError
│ │ ├── ConnectionRefusedError
│ │ └── ConnectionResetError
│ ├── FileExistsError
│ ├── FileNotFoundError
│ ├── InterruptedError
│ ├── IsADirectoryError
│ ├── NotADirectoryError
│ ├── PermissionError
│ ├── ProcessLookupError
│ └── TimeoutError
├── ReferenceError
├── RuntimeError
│ ├── NotImplementedError
│ └── RecursionError
├── StopAsyncIteration
├── StopIteration
├── SyntaxError
│ └── IndentationError
│ └── TabError
├── SystemError
├── TypeError
├── ValueError
│ └── UnicodeError
│ ├── UnicodeDecodeError
│ ├── UnicodeEncodeError
│ └── UnicodeTranslateError
└── Warning
├── BytesWarning
├── DeprecationWarning
├── EncodingWarning
├── FutureWarning
├── ImportWarning
├── PendingDeprecationWarning
├── ResourceWarning
├── RuntimeWarning
├── SyntaxWarning
├── UnicodeWarning
└── UserWarning
Обратите внимание, что большинство классов в иерархии наследуют от Exception
. Это также базовый класс, который следует использовать в тех ситуациях, когда вам нужно создать собственное исключение.
Знание базовых исключений
Во встроенной иерархии исключений вы найдете несколько классов, которые созданы как базовые. Класс BaseException
находится вверху. Тогда у вас есть пять подклассов:
BaseExceptionGroup
Создает группу исключений, которая охватывает любое исключение, а не только те, которые наследуются от
Exception
.GeneratorExit
Происходит при закрытии генератора или сопрограммы
KeyboardInterrupt
Происходит, когда пользователь нажимает комбинацию клавиш прерывания, обычно это Ctrl<span>+C
SystemExit
Результаты вызова функции
sys.exit()
, но вы также можете вызвать ее напрямую.Exception
Предоставляет базовый класс для пользовательских исключений, который должен быть производным от этого класса.
Как видите, все эти базовые исключения имеют свой конкретный вариант использования. Важно отметить, что при создании пользовательских исключений следует использовать Exception
, а не BaseException
. Это потому, что BaseException
следует использовать для исключений, которые никогда не должны перехватываться в реальном коде.
На практике при перехвате и вызове исключений следует использовать наиболее конкретное исключение для рассматриваемой проблемы.
Например, если у вас есть фрагмент кода, который потенциально может вызвать ValueError
, вам следует явно обработать это исключение. Если вы используете Exception
вместо ValueError
, ваш код перехватит Exception
и все его подклассы, включая ValueError
. Если ваш код в конечном итоге вызовет что-то отличное от ValueError
, эта ошибка будет обработана неправильно.
Знакомство с предупреждениями
В нижней части иерархии исключений вы найдете предупреждения. Это особые типы исключений, которые обозначают что-то, что может вызвать проблемы в ближайшем будущем. Предупреждения — это исключения, относящиеся к категориям предупреждений.
Примечание. Поскольку предупреждения — это своего рода исключения, имеющие особые случаи использования и специальную документацию, вы не будете подробно рассматривать их в этом руководстве. Вы можете проверить страницу управления предупреждениями в документации Python, чтобы получить полное описание предупреждений.
Вероятно, самое распространенное предупреждение, которое вы увидите при выполнении кода Python, — это DeprecationWarning
. Это предупреждение появляется, когда вы используете устаревшие функции языка. Например, в Python 3.12 устарели константы calendar.January
и calendar.February
:
>>> # Python 3.12.2
>>> calendar.January
<stdin>:1: DeprecationWarning: The 'January' attribute is deprecated,
use 'JANUARY' instead
1
Несмотря на то, что код работает, поскольку константы еще не удалены, Python сообщает вам, что эта функция устарела, чтобы у вас не возникло проблем в будущем. Как сказано в предупреждающем сообщении, теперь вам следует использовать calendar.JANUARY
.
Синтаксические ошибки
Первое исключение, которое вы, вероятно, увидите в Python, — это исключение SyntaxError
. Несмотря на то, что Python использует класс исключений для такого типа проблем, вы должны понимать, что они представляют собой ошибки, а не исключения. Таким образом, вы будете не разбираться с ними, а исправлять их, как вы уже узнали.
Вы также обнаружите, что Python определяет пару дополнительных исключений, наследуемых от SyntaxError
:
IndentationError
TabError
Эти исключения можно ожидать, когда вы начинаете изучать Python, и они могут вас сбить с толку. К счастью, современные редакторы кода и IDE включают функции, которые обнаруживают и часто устраняют условия, вызывающие эти исключения. В следующих разделах вы узнаете об этой группе исключений.
Синтаксическая ошибка
Когда Python обнаруживает недопустимый синтаксис в фрагменте кода, он вызывает исключение SyntaxError
. Исключение печатает обратную трассировку с полезной информацией, которую можно использовать для отладки ошибки и исправления кода.
Примечание. Чтобы узнать больше о синтаксических ошибках, ознакомьтесь с руководством «Неверный синтаксис в Python: общие причины синтаксических ошибок».
Существует множество ситуаций, когда в коде может возникнуть синтаксическая ошибка. Вы можете забыть запятую или закрывающую скобку, неправильно использовать оператор или ключевое слово и многое другое.
Вот несколько примеров:
>>> numbers = [1, 2, 3 4]
File "<stdin>", line 1
numbers = [1, 2, 3 4]
^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
>>> 7 = 7
File "<stdin>", line 1
7 = 7
^
SyntaxError: cannot assign to literal here.
Maybe you meant '==' instead of '='?
>>> class = "Economy"
File "<stdin>", line 1
class = "Economy"
^
SyntaxError: invalid syntax
Это распространенные синтаксические ошибки, с которыми вы можете иногда столкнуться. Хорошей новостью является то, что благодаря улучшенным сообщениям об ошибках, которые Python предоставляет в наши дни, вы можете быстро отследить ошибку и исправить ее.
IndentationError
В отличие от других языков программирования, отступы являются частью синтаксиса Python. Чтобы разграничить блок кода в Python, вы используете отступ. Поэтому вы можете получить сообщение об ошибке, если отступ неправильный. Python использует исключение IndentationError
для обозначения этой проблемы.
Вы можете столкнуться с этим исключением, когда начинаете изучать Python. Он также может появиться при копировании и вставке кода из сложных мест. К счастью, в настоящее время эта ошибка встречается нечасто, поскольку современные редакторы кода могут автоматически исправлять отступы кода.
Чтобы увидеть исключение в действии, рассмотрим следующий пример функции, блок кода которой использует неравномерные отступы:
>>> def greet(name):
... print(f"Hello, {name}!")
... print("Welcome to Real Python!")
File "<stdin>", line 3
print("Welcome to Real Python!")
^
IndentationError: unindent does not match any outer indentation level
В этом примере выделенные строки имеют разные отступы. Первая строка имеет отступ в четыре пробела, а вторая строка — в два пробела. Это несоответствие отступов вызывает ошибку IndentationError
. Опять же, поскольку это синтаксическая ошибка, Python немедленно сообщает об этом. Вы можете быстро решить проблему, исправив отступ.
Ошибка табуляции
Другая возможная проблема для тех, кто работает с другими языками программирования, — это смешивание символов табуляции и пробела при отступах в коде Python. Исключением для этой проблемы является TabError
, который является подклассом IndentationError
. Итак, это еще одно исключение из синтаксической ошибки.
Примечание. В Руководстве по стилю кода Python (PEP 8) прямо говорится:
Пробелы являются предпочтительным методом отступов. Табуляции следует использовать исключительно для обеспечения соответствия коду, в котором уже есть отступы. Python запрещает смешивать табуляции и пробелы для отступов. (Источник)
Вот пример, когда Python вызывает исключение TabError
:
def greet(name):
print(f"Hello, {name}!")
print("Welcome to Real Python!")
В этом примере первая строка в greet()
имеет отступ с использованием символа табуляции, а вторая строка имеет отступ с использованием восьми пробелов, что является обычным количеством пробелов, заменяющих символ табуляции.
Примечание. Если вы не используете восемь пробелов во второй строке, вы получите IndentationError
вместо TabError
.
Чтобы опробовать пример, запустите файл из командной строки:
$ python greeting.py
File ".../greeting.py", line 3
print("Welcome to Real Python!")
TabError: inconsistent use of tabs and spaces in indentation
Поскольку в отступах кода используются символы табуляции и пробелы, вы получаете ошибку отступа TabError
. Опять же, исключения TabError
в настоящее время не распространены, поскольку современные редакторы кода могут их исправлять автоматически. Итак, если ваш редактор хорошо настроен для разработки на Python, вы, вероятно, не увидите это исключение в действии.
Исключения, связанные с импортом
Иногда при импорте пакетов, модулей и их содержимого ваш код завершается с ошибкой, сообщающей, что целевой файл не найден. На практике вы можете получить одно из двух исключений, связанных с импортом:
ModuleNotFoundError
ImportError
Обратите внимание, что исключение ModuleNotFoundError
является подклассом ImportError
с более конкретным значением или целью, как вы вскоре узнаете.
В следующих разделах вы узнаете об этих двух исключениях и о том, когда Python их вызывает. Эти знания помогут вам решить проблему и заставить ваш код работать.
ModuleNotFoundError
Как вы уже узнали, исключение ModuleNotFoundError
является подклассом ImportError
и имеет более конкретную цель. Python вызывает это исключение, когда не может найти модуль, из которого вы хотите что-то импортировать:
>>> import non_existing
Traceback (most recent call last):
...
ModuleNotFoundError: No module named 'non_existing'
Когда Python не находит целевой модуль в пути поиска импорта, он вызывает исключение ModuleNotFoundError
. Чтобы решить эту проблему, вы должны убедиться, что ваш модуль указан в sys.path
.
Примечание. Лучший способ убедиться, что ваш модуль доступен по пути Python, — это установить его. Вы можете использовать pip
и устанавливать локальные пакеты аналогично тому, как вы устанавливаете пакеты из PyPI.
Поскольку ModuleNotFoundError
является подклассом ImportError
, когда вы явно используете последний в блоке try
… кроме
, вы буду ловить оба исключения. Итак, если вы намерены отследить те ситуации, когда целевой модуль отсутствует, вам следует быть конкретным и использовать ModuleNotFoundError
.
Ошибка импорта
Python вызывает исключение ImportError
для всех проблем, связанных с импортом, которые не рассматриваются в ModuleNotFoundError
. Это может произойти по двум основным причинам:
- Оператору
импорт модуля
не удается загрузить модуль по причине, не описанной вModuleNotFoundError
. - Оператору
имя импорта модуля
не удается найтиимя
в целевом модуле.
Теперь запустите следующий оператор import
, чтобы увидеть исключение ImportError
в действии:
>>> from sys import non_existing
Traceback (most recent call last):
...
ImportError: cannot import name 'non_existing' from 'sys' (unknown location)
В этом примере вы пытаетесь импортировать имя non_existing
из модуля sys
. Поскольку имя не существует в этом модуле, вы получаете ImportError
. Вы можете быстро решить проблему, указав правильное имя цели.
Примечание. Чтобы глубже узнать, как работает импорт в Python, ознакомьтесь с руководством по импорту Python: дополнительные методы и советы.
В реальном коде вы можете воспользоваться преимуществами ImportError
или ModuleNotFoundError
для дополнительной загрузки различных модулей или библиотек, которые предоставляют заданную функциональность в зависимости от доступности библиотеки.
Например, предположим, что вам нужно проанализировать файл TOML и прочитать его содержимое. В этом случае вы можете использовать модуль стандартной библиотеки tomllib
, если вы используете Python 3.11 или новее. В противном случае вам следует использовать стороннюю библиотеку tomli
, совместимую с tomllib
.
Вот как это можно сделать:
try:
import tomllib # Python >= 3.11
except ModuleNotFoundError:
import tomli as tomllib # Python < 3.11
Импорт в предложении try
предназначен для модуля стандартной библиотеки tomllib
. Если этот импорт вызывает исключение, поскольку вы используете версию Python ниже 3.11, то предложение кроме
импортирует стороннюю библиотеку tomli
, которую вам необходимо установить как внешняя зависимость вашего проекта.
Исключения ошибок поиска
Получение IndexError
при выполнении операций индексирования последовательности или KeyError
при поиске ключей в словарях также является распространенной проблемой в Python. В следующих разделах вы узнаете об этих двух исключениях и о том, когда они могут возникнуть в вашем коде.
Ошибка индекса
Исключение IndexError
возникает, когда вы пытаетесь получить значение из последовательности, используя индекс, выходящий за пределы диапазона:
>>> colors = [
... "red",
... "orange",
... "yellow",
... "green",
... "blue",
... "indigo",
... "violet",
... ]
>>> colors[10]
Traceback (most recent call last):
File "<input>", line 1, in <module>
colors[10]
~~~~~~^^^^
IndexError: list index out of range
В этом примере вы используете 10
в качестве индекса для получения значения из списка colors
. Поскольку в списке всего семь элементов, допустимые индексы варьируются от 0
до 6
. Ваш целевой индекс выходит за пределы допустимого диапазона, и Python выдает исключение IndexError
.
IndexError
может быть частым исключением в Python, особенно если вы используете динамически генерируемые индексы. Чтобы устранить проблему, вам необходимо выяснить, какой индекс использует ваш код для получения значения из целевой последовательности, а затем настроить диапазон индекса, чтобы исправить проблему.
Примечание. В большинстве ситуаций вы не будете использовать индексы в циклах Python. Из-за такой практики проблема с ошибкой индекса в циклах Python встречается реже, чем в других языках программирования, где циклы основаны на индексах.
Если вы не можете контролировать диапазон индексов, вы можете перехватить исключение в блоке try
… except
и предпринять соответствующие действия по восстановлению.
Вы также можете вызвать исключение IndexError
в своем собственном коде. Это исключение может быть уместным, когда вы создаете собственные структуры данных, подобные последовательностям. Например, предположим, что вам нужно создать стек, похожий на последовательность:
class Stack:
def __init__(self, items=None):
self.items = list(items) if items is not None else []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __len__(self):
return len(self.items)
def __getitem__(self, index):
try:
return self.items[index]
except IndexError:
raise IndexError(
f"your stack only has {len(self)} items!"
) from None
В этом примере вы определяете Stack
и предоставляете методы .push()
и .pop()
. Первый метод добавляет новый элемент на вершину стека, а второй удаляет и возвращает элемент на вершину стека.
Затем у вас есть специальный метод .__len__()
для поддержки встроенной функции len()
, которая дает вам количество элементов в стеке.
Наконец, у вас есть метод .__getitem__()
. Этот специальный метод принимает индекс в качестве аргумента и возвращает элемент по входному индексу. Блок try
… кроме
перехватывает исключение IndexError
и повторно вызывает его с более конкретным сообщением об ошибке. Вы не полагаетесь на исходное сообщение, что делает ваш код более целенаправленным.
Ошибка ключа
Аналогично, когда вы пытаетесь получить несуществующий ключ из словаря, вы получаете исключение KeyError
. Эта проблема также распространена в Python:
>>> fruits = {"apple": 0.40, "orange": 0.35, "banana": 0.25}
>>> fruits["grape"]
Traceback (most recent call last):
File "<input>", line 1, in <module>
fruits["grape"]
~~~~~~^^^^^^^^^
KeyError: 'grape'
В этом примере вы пытаетесь получить ключ grape
из словаря fruits
и получаете исключение KeyError
. В этом случае сообщение об ошибке довольно короткое. Он отображает только имя отсутствующего ключа.
Опять же, источником проблемы может быть использование динамически генерируемых ключей. Итак, чтобы исправить проблему, вам необходимо проверить сгенерированные и существующие ключи в вашем коде. Альтернативно вы можете использовать метод .get()
с соответствующим значением по умолчанию, если это подходящее решение для вашего варианта использования.
Наконец, вы также можете вызвать KeyError
в своем коде. Это может быть полезно, когда вам нужно создать класс, похожий на словарь, и предоставить пользователям более подробное сообщение об ошибке.
Ошибки имени: NameError
Исключение NameError
также довольно распространено, когда вы начинаете работать с Python. Это исключение возникает, когда вы пытаетесь использовать имя, которого нет в вашем текущем пространстве имен. Итак, это исключение тесно связано с областями действия и пространствами имен.
Например, предположим, что вы работаете в сеансе REPL. Вы импортировали модуль sys
, чтобы использовать его в текущей задаче. По какой-то причине вы перезапускаете интерактивный сеанс и пытаетесь использовать sys
без его повторного импорта:
>>> sys.path
Traceback (most recent call last):
File "<input>", line 1, in <module>
sys.path
^^^
NameError: name 'sys' is not defined. Did you forget to import 'sys'?
Поскольку вы перезапустили интерактивный сеанс, все имена и модули, которые вы импортировали ранее, исчезли. Теперь, когда вы пытаетесь использовать модуль sys
, вы получаете ошибку NameError
.
Обратите внимание: если неизвестное имя находится после точки в полном имени, вы не увидите сообщение NameError
. В этом случае вместо этого вы получите исключение AttributeError
, о котором вы узнаете в следующем разделе.
Объектно-связанные исключения
Вы обнаружите несколько исключений, которые могут возникнуть при работе с классами, объектами и встроенными типами Python. Наиболее распространенными являются следующие:
TypeError
ValueError
AttributeError
В следующих разделах вы узнаете, когда возникают такие исключения и как с ними бороться в коде Python.
Ошибка типа
Python генерирует исключение TypeError
, когда вы применяете операцию или функцию к объекту, который не поддерживает эту операцию. Например, рассмотрим вызов встроенной функции len()
с числом в качестве аргумента:
>>> len(42)
Traceback (most recent call last):
File "<input>", line 1, in <module>
len(42)
TypeError: object of type 'int' has no len()
В этом примере аргумент len()
представляет собой целое число, которое не поддерживает эту функцию. Поэтому вы получаете ошибку с соответствующим сообщением.
На практике вы можете перехватить исключение TypeError
в своем собственном коде, когда вам нужно предотвратить проблему, связанную с типом. Вы также можете вызвать исключение в своем коде, чтобы обозначить проблему, связанную с типом. Например, предположим, что вы хотите написать функцию, которая принимает итерацию чисел и возвращает список квадратов значений. Вы хотите убедиться, что функция принимает только итеративные объекты.
В этой ситуации вы можете использовать следующую функцию:
>>> def squared(numbers):
... try:
... iter(numbers)
... except TypeError:
... raise TypeError(
... f"expected an iterable, got '{type(numbers).__name__}'"
... ) from None
... return [number**2 for number in numbers]
...
>>> squared([1, 2, 3, 4, 5])
[1, 4, 9, 16, 25]
>>> squared(42)
Traceback (most recent call last):
...
TypeError: expected an iterable, got 'int'
В этом примере ваша функция использует встроенную функцию iter()
для проверки входных данных. Эта функция вызывает исключение TypeError
, если предоставленный ввод не является итеративным. Вы перехватываете это исключение и вызываете его повторно, но с собственным сообщением об ошибке, которое соответствует назначению вашего кода. Затем вы создаете список квадратов значений, используя понимание списка.
ValueError
Аналогично, Python вызывает исключение ValueError
, когда операция или функция получает аргумент правильного типа, но неподходящего значения:
>>> float("1")
1.0
>>> float("one")
Traceback (most recent call last):
...
ValueError: could not convert string to float: 'one'
В этом примере вы сначала используете встроенную функцию float()
для преобразования строки в число с плавающей запятой. Входная строка содержит числовое значение, поэтому операция завершается успешно.
Во втором примере вы вызываете ту же функцию, используя строку, которая не представляет допустимого числа, и получаете исключение ValueError
. Обратите внимание, что входной аргумент представляет собой строку, которая является правильным типом аргумента. Однако входная строка не является допустимым числовым значением, поэтому у вас правильный тип, но неправильное значение.
Вы также можете использовать ValueError
в своем коде Python. Это исключение может быть уместно в разных ситуациях. Например, предположим, что вам нужно написать класс для представления цветов радуги. Этот класс имеет семь разрешенных названий цветов, которые можно представить с помощью строк:
COLORS = {
"Red": {"Hex": "#FF0000", "RGB": (255, 0, 0)},
"Orange": {"Hex": "#FF7F00", "RGB": (255, 127, 0)},
"Yellow": {"Hex": "#FFFF00", "RGB": (255, 255, 0)},
"Green": {"Hex": "#00FF00", "RGB": (0, 255, 0)},
"Blue": {"Hex": "#0000FF", "RGB": (0, 0, 255)},
"Indigo": {"Hex": "#4B0082", "RGB": (75, 0, 130)},
"Violet": {"Hex": "#8B00FF", "RGB": (139, 0, 255)},
}
class RainbowColor:
def __init__(self, name="Red"):
name = name.title()
if name not in COLORS:
raise ValueError(f"{name} is not a valid rainbow color")
self.name = name
def as_hex(self):
return COLORS[self.name]["Hex"]
def as_rgb(self):
return COLORS[self.name]["RGB"]
В этом примере вы сначала определяете константу COLORS
, которая содержит цвета радуги и соответствующие им шестнадцатеричные и RGB-представления.
Затем вы определяете класс RainbowColor
. В инициализаторе класса вы используете условие, чтобы убедиться, что имя входного цвета является одним из цветов радуги. Если это не так, вы вызываете ошибку ValueError
, поскольку имя цвета имеет правильный тип, но имеет неправильное значение. Это строка, но недопустимое имя цвета.
Вот как этот класс работает на практике:
>>> from rainbow import RainbowColor
>>> color = RainbowColor("Gray")
Traceback (most recent call last):
...
ValueError: Gray is not a valid rainbow color
>>> color = RainbowColor("Blue")
>>> color.name
'Blue'
>>> color.as_rgb()
(0, 0, 255)
В этом фрагменте кода при попытке передать недопустимое имя цвета вы получаете ValueError
. Если имя цвета допустимо, класс работает нормально.
AttributeError
Еще одно распространенное исключение, которое вы увидите при работе с классами Python, — это AttributeError
. Это исключение возникает, когда указанный объект не определяет атрибут или метод, к которому вы пытаетесь получить доступ.
Возьмем, к примеру, ваш RainbowClass
из предыдущего раздела. Этот класс имеет один атрибут и два метода: .name
, .as_hex()
и .as_rgb()
. Если вы получите к ним доступ, вы получите результат в соответствии с тем, что они делают. Однако предположим, что вы пытаетесь получить доступ к методу под названием .as_hsl()
. Что случилось бы?
Вот ответ:
>>> from rainbow import RainbowColor
>>> color = RainbowColor("Blue")
>>> color.as_hsl()
Traceback (most recent call last):
...
AttributeError: 'RainbowColor' object has no attribute 'as_hsl'
Поскольку RainbowColor
не определяет метод .as_hsl()
, вы получаете AttributeError
, который сообщает вам, что у класса нет атрибута, который соответствует этому имени.
Эта ошибка может быть распространена в сложных иерархиях классов, которые определяют похожие классы с немного разными интерфейсами. В этой ситуации вы можете подумать, что данный класс определяет данный метод или атрибут, но это может быть не так. Чтобы устранить эту проблему, вы можете просмотреть определение или документацию класса и убедиться, что вы используете только те атрибуты и методы, которые определяет класс.
Вы можете пропустить исключение AttributeError
, используя встроенную функцию hasattr()
:
>>> if hasattr(color, "as_hsl"):
... color.as_hsl()
... else:
... print("Hmm, you can't call that.")
...
Hmm, you can't call that.
В этом примере вы используете hasattr()
, чтобы явно проверить, имеет ли объект color
атрибут с именем "as_hsl"
.
Вы также можете перехватить и вызвать исключение AttributeError
в своих пользовательских классах. На практике это исключение может быть весьма полезным, поскольку Python рекомендует использовать стиль кодирования, основанный на обработке исключений, называемый EAFP (проще попросить прощения, чем разрешения).
Короче говоря, использование этого стиля означает, что вы решаете обрабатывать ошибки и исключения, когда они происходят. Напротив, при использовании стиля LBYL (посмотри, прежде чем прыгать) вы пытаетесь предотвратить ошибки и исключения до того, как они произойдут. Это то, что вы сделали в примере выше.
Итак, вместо того, чтобы полагаться на hasattr()
, вам следует написать приведенный выше пример следующим образом:
>>> try:
... color.as_hsl()
... except AttributeError:
... print("Hmm, you can't call that.")
...
Hmm, you can't call that.
Этот стиль кодирования более прямой. Вы идете и вызываете нужный метод. Если это не удается с ошибкой AttributeError
, вы выполняете альтернативные действия.
Наконец, вы также получите исключение AttributeError
при попытке доступа к объекту, который не определен в уже импортированном модуле:
>>> import sys
>>> sys.non_existing
Traceback (most recent call last):
...
AttributeError: module 'sys' has no attribute 'non_existing'
Обратите внимание, что в этом примере вы не получите ошибку импорта. Вместо этого вы получаете исключение AttributeError
. Это связано с тем, что объекты, определенные в модуле, становятся атрибутами этого модуля.
Исключения из арифметических ошибок
Класс исключений ArithmeticError
— это базовый класс для встроенных исключений, связанных с тремя различными типами арифметических ошибок:
ZeroDivisionError
FloatingPointError
OverflowError
Из этих трех исключений наиболее распространенным является ZeroDivisionError
. FloatingPointError
определяется как встроенное исключение, но в настоящее время Python его не использует. В следующих разделах вы узнаете, когда в вашем коде могут появиться исключения ZeroDivisionError
и OverflowError
.
ZeroDivisionError
Python вызывает исключение ZeroDivisionError
, когда делитель или правый операнд деления равен нулю. В качестве быстрого примера предположим, что вам нужно написать функцию для вычисления средней оценки группы учащихся. В этом случае можно придумать следующую функцию:
>>> def average_grade(grades):
... return sum(grades) / len(grades)
...
>>> average_grade([4, 3, 3, 4, 5])
3.8
Эта функция работает нормально, если вы используете соответствующие входные данные. Но что произойдет, если вы вызовете функцию с пустым объектом списка? Вот ответ:
>>> average_grade([])
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
Поскольку список ввода пуст, функция len()
возвращает 0
. Это возвращаемое значение создает исключение ZeroDivisionError
при фактическом делении.
Чтобы устранить проблему, вы можете перехватить исключение и предоставить пользователю описательное сообщение об ошибке:
>>> def average_grade(grades):
... try:
... return sum(grades) / len(grades)
... except ZeroDivisionError:
... raise ValueError(
... "you should pass at least one grade to average_grade()"
... ) from None
...
>>> average_grade([])
Traceback (most recent call last):
...
ValueError: you should pass at least one grade to average_grade()
В этой обновленной версии average_grade()
вы улавливаете ZeroDivisionError
, который генерирует пустой список ввода, и вызываете ValueError
с понятной для пользователя ошибкой. сообщение.
Наконец, исключение ZeroDivisionError
также возникает, когда правый операнд операции по модулю равен нулю:
>>> 42 % 0
Traceback (most recent call last):
File "<input>", line 1, in <module>
42 % 0
~~~^~~
ZeroDivisionError: integer modulo by zero
Оператор по модулю возвращает остаток от деления двух чисел. Поскольку деление чисел — это первый шаг к вычислению остатка, вы можете получить ZeroDivisionError
, если правый операнд равен 0
.
Ошибка переполнения
Ошибка OverflowError
встречается не так уж и часто. Python вызывает это исключение, когда результат арифметической операции слишком велик и его невозможно представить:
>>> 10.0**1000
Traceback (most recent call last):
File "<input>", line 1, in <module>
10.0**1000
~~~~^^~~~~
OverflowError: (34, 'Result too large')
В этом примере число, полученное в результате операции степени, слишком велико, и Python не может его правильно представить. Итак, вы получаете исключение OverflowError
.
Исключения, связанные с итерацией
В Python есть несколько встроенных исключений, тесно связанных с итерацией. RecursionError
относится к рекурсивному коду и является тем, что можно назвать стандартным исключением. Затем у вас есть исключение StopIteration
, которое Python внутренне использует для управления потоком выполнения циклов for
. У этого исключения есть асинхронный аналог, называемый AsyncStopIteration
, который Python использует в циклах async for
.
Примечание. Исключение AsyncStopIteration
имеет то же семантическое значение, что и исключение StopIteration
, но для асинхронных циклов. Поэтому это исключение не будет рассматриваться в этом руководстве.
В следующих разделах вы узнаете об исключениях RecursionError
и StopIteration
и о том, как их использует Python.
Ошибка рекурсии
Функция, вызывающая сама себя, называется рекурсивной функцией. Эти типы функций являются основой метода итерации, называемого рекурсия.
Примечание. Чтобы узнать больше о рекурсии, ознакомьтесь с руководством «Рекурсия в Python: введение».
Рассмотрим следующую игрушечную функцию как пример рекурсивной функции:
>>> def recurse():
... recurse()
...
Эта функция не следует правилам рекурсии, поскольку у нее нет базового варианта, останавливающего повторение. Он имеет только рекурсивный случай. Итак, если вы вызовете функцию, вы получите исключение RecursionError
:
>>> recurse()
Traceback (most recent call last):
File "<input>", line 1, in <module>
recurse()
File "<input>", line 2, in recurse
recurse()
File "<input>", line 2, in recurse
recurse()
File "<input>", line 2, in recurse
recurse()
[Previous line repeated 981 more times]
RecursionError: maximum recursion depth exceeded
Python вызовет исключение RecursionError
, когда интерпретатор обнаружит, что максимальная глубина рекурсии превышена. Это может произойти, когда ваш базовый вариант не работает должным образом или когда необходимое количество рекурсий для выполнения вычислений превышает предел рекурсии.
Примечание. Ограничение рекурсии необходимо, поскольку каждый вызов функции занимает память в стеке вызовов вашей системы, поэтому это способ контролировать использование памяти.
Вы можете использовать функцию sys.getrecursionlimit()
, чтобы проверить текущую глубину рекурсии:
>>> import sys
>>> sys.getrecursionlimit()
1000
Результатом вызова этой функции является число, показывающее, сколько раз рекурсивная функция может вызвать сама себя в Python. Если это подходит для вашего случая использования, вы также можете установить другой предел рекурсии с помощью функции sys.setrecursionlimit()
.
Остановить итерацию
Python вызывает исключение StopIteration
, когда вы вызываете встроенную функцию next()
на исчерпанном итераторе. Его цель — сигнализировать о том, что в итераторе больше нет элементов. Итак, Python использует это исключение внутри себя для управления итерацией.
Рассмотрим следующий пример игрушки:
>>> numbers_iterator = iter([1, 2, 3])
>>> next(numbers_iterator)
1
>>> next(numbers_iterator)
2
>>> next(numbers_iterator)
3
>>> next(numbers_iterator)
Traceback (most recent call last):
File "<input>", line 1, in <module>
next(numbers_iterator)
StopIteration
В этом примере вы используете встроенную функцию iter()
для создания итератора из короткого списка чисел. Затем вы повторно вызываете next()
, чтобы использовать итератор. Четвертый вызов этой функции вызывает исключение StopIteration
, сигнализирующее о том, что у итератора больше нет элементов для извлечения.
Внутри Python использует это исключение для завершения циклов for
. Другими словами, когда цикл for
выполняет итерацию по итератору, цикл останавливается, когда он получает исключение StopIteration
. По этой причине это исключение нечасто можно увидеть в действии. Обратите внимание, что цикл Python for
вызывает next()
для выполнения итерации.
На практике вы можете использовать исключение StopIteration
в своем коде как часть реализации протокола итератора. Этот протокол состоит из двух специальных методов:
.__iter__()
вызывается для инициализации итератора. Он должен возвращать объект-итератор..__next__()
вызывается для перебора итератора. Он должен вернуть следующее значение в потоке данных.
Когда вы предоставляете эти методы в пользовательском классе, ваш класс поддерживает итерацию. Чтобы сигнализировать о необходимости остановки итерации, вы должны вызвать исключение StopIteration
в методе .__next__()
.
Примечание. Чтобы узнать больше об итераторах в Python, ознакомьтесь с учебным пособием «Итераторы и итерируемые объекты в Python: запуск эффективных итераций».
Чтобы проиллюстрировать, как использовать StopIteration
, предположим, что вам нужно создать собственный класс, который принимает список значений и создает итератор по их квадратам:
class SquareIterator:
def __init__(self, values):
self.values = values
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.values):
raise StopIteration
square = self.values[self.index] ** 2
self.index += 1
return square
В этом классе инициализатор принимает список значений в качестве аргумента. Он также определяет ссылочный индекс, который начинается с 0
. Метод .__iter__()
возвращает self
, что является обычной практикой при использовании протокола итератора, как в этом примере.
В методе .__next__()
вы проверяете, превышает ли текущий индекс количество значений во входном списке или равен ему. В этом случае вы вызываете StopIteration
, чтобы сигнализировать о том, что итератор исчерпан. Затем вы вычисляете квадрат текущего значения и возвращаете его как результат метода.
Теперь вы можете использовать экземпляры этого класса в цикле for
:
>>> from squares import SquareIterator
>>> for value in SquareIterator([1, 2, 3]):
... print(value)
...
1
4
9
Ваш итератор работает нормально. Он генерирует квадратичные значения и поддерживает итерацию. Обратите внимание, что вы не видите возникновения исключения StopIteration
. Однако Python использует это исключение внутри себя для завершения цикла, когда итератор завершен.
Исключения, связанные с файлами
При обработке файлов в вашем коде Python вы можете столкнуться с несколькими различными проблемами. Ваш код может попытаться создать файл, который уже существует, получить доступ к несуществующему файлу, запустить файловую операцию в каталоге, а не в файле, или иметь проблемы с разрешениями. В Python есть исключения, связанные с файлами, которые отмечают такие ситуации.
Вот некоторые из самых популярных:
FileExistsError
FileNotFoundError
PermissionError
В следующих разделах вы узнаете об этих встроенных исключениях и о том, когда они возникают в Python.
FileExistsError
Когда вы пытаетесь создать уже существующий файл или каталог, Python вызывает встроенное исключение FileExistsError
.
Чтобы увидеть это исключение в действии, предположим, что вам нужно создать файл в рабочем каталоге и записать в него некоторый текст. Однако не следует заменять файл, если он уже существует. В этом случае вы можете воспользоваться FileExistsError
и написать следующий код:
try:
with open("hello.txt", mode="x") as file:
file.write("Hello, World!")
except FileExistsError:
print("The file already exists")
В этом примере вы используете встроенную функцию open()
, чтобы открыть файл с именем hello.txt
. Режим "x"
означает, что вы хотите открыть файл для эксклюзивного создания, но это не удастся, если файл уже существует.
Теперь запустите этот файл hello.py
из командной строки:
$ python hello.py
Эта команда не выдаст никакого вывода. Однако, если вы посмотрите на свой рабочий каталог, вы увидите новый файл с именем hello.txt
с текстом "Hello, World!"
в нем.
Если вы запустите команду еще раз, то результат будет другим:
$ python hello.py
The file already exists
На этот раз, поскольку файл hello.txt
уже существует, код создания файла вызывает исключение FileExistsError
, а предложение Exception
выводит ошибку. сообщение на экране.
FileNotFoundError
Python вызывает исключение FileNotFoundError
, когда файл или каталог запрошен, но не существует в целевом расположении. Например, предположим, что вы случайно удалили файл hello.txt
, созданный в предыдущем разделе. При попытке прочитать содержимое файла выдается ошибка:
>>> with open("hello.txt", mode="r") as file:
... print(file.read())
...
Traceback (most recent call last):
File "<input>", line 1, in <module>
with open("hello.txt", mode="r") as file:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'hello.txt'
В этом фрагменте кода вы пытаетесь открыть файл hello.txt
, чтобы прочитать его содержимое. Поскольку вы удалили файл до этой операции, ваш код завершится с ошибкой FileNotFoundError
.
Чтобы решить эту проблему, вы можете переписать код, как показано в следующем примере:
>>> try:
... with open("hello.txt", mode="r") as file:
... print(file.read())
... except FileNotFoundError:
... print("The file doesn't exist")
...
The file doesn't exist
Теперь вы используете блок try
… Exception
для обработки исключения FileNotFoundError
и предотвращения выхода вашего кода из строя.
Ошибка разрешения
Другая распространенная проблема при обработке файлов — это попытка получить доступ к файлу или каталогу без соответствующих прав доступа. В этом случае Python генерирует исключение PermissionError
, чтобы вы знали, что у вас нет необходимых разрешений.
Например, предположим, что вы работаете в Unix-подобной системе, такой как Linux или macOS. Вы хотите обновить содержимое файла /etc/hosts
. В Unix-подобных системах только пользователь root может изменять этот файл. Итак, если вы попытаетесь выполнить свою задачу от имени другого пользователя, вы получите сообщение об ошибке:
>>> with open("/etc/hosts", mode="a") as file:
... print(file.write("##"))
...
Traceback (most recent call last):
File "<input>", line 1, in <module>
with open("/etc/hosts", mode="a") as file:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
PermissionError: [Errno 13] Permission denied: '/etc/host'
Эта ошибка возникает, поскольку файл /etc/hosts
является системным файлом. Для изменения этого файла вам нужны права root. Ваш код пытается открыть файл для добавления содержимого, не имея необходимых разрешений, что приводит к исключению PermissionError
.
Исключение, связанное с абстрактными классами: NotImplementedError
Иногда вам нужно создать собственный базовый класс с предопределенным интерфейсом, из которого ваши пользователи могут создавать свои собственные классы. Таким образом, вы гарантируете, что подклассы соответствуют требуемому интерфейсу. В Python вы можете сделать это, используя так называемый абстрактный базовый класс (ABC).
Модуль abc
в стандартной библиотеке предоставляет класс ABC
и другие связанные инструменты, которые можно использовать для определения пользовательских базовых классов, определяющих определенный интерфейс. Обратите внимание, что вы не можете создавать экземпляры ABC напрямую. Они предназначены для подклассов.
Когда вы начнете создавать свои ABC, вы обнаружите, что при определении методов обычной практикой является создание исключения NotImplementedError
для тех методов, которые должны быть реализованы подклассами, но это не является строгим требованием.
В качестве примера предположим, что вы хотите создать иерархию классов для представления различных птиц, таких как утка, лебедь, пингвин и т. д. Вы решаете, что все классы должны иметь методы .swim()
и .fly()
. В этой ситуации вы можете начать со следующего базового класса:
from abc import ABC
class Bird(ABC):
def swim(self):
raise NotImplementedError("must be implemented in subclasses")
def fly(self):
raise NotImplementedError("must be implemented in subclasses")
В этом классе Bird
вы наследуетесь от abc.ABC
, что означает, что вы создаете абстрактный базовый класс. Затем вы определяете методы .swim()
и .fly()
, которые вызывают исключение NotImplementedError
с соответствующим сообщением об ошибке.
Примечание. Вы также можете импортировать abstractmethod
из abc
и украсить .swim()
и .fly( )
с помощью @abstractmethod
. Python выдаст ошибку, если вы создадите экземпляр любого класса с помощью абстрактного метода.
Вот пример того, как можно получить конкретных птиц из приведенного выше класса:
# ...
class Duck(Bird):
def swim(self):
print("The duck is swimming")
def fly(self):
print("The duck is flying")
class Penguin(Bird):
def swim(self):
print("The penguin is swimming")
В этом примере вы создаете класс Duck
с помощью методов .swim()
и .fly()
. Затем вы создаете класс Penguin
, который имеет только метод .swim()
, поскольку пингвины не умеют летать. Вы можете использовать класс Duck
как обычно. Напротив, класс Penguin
будет вести себя по-другому, когда вы вызываете его метод .fly()
:
>>> from birds import Duck
>>> donald = Duck()
>>> donald.swim()
The duck is swimming
>>> donald.fly()
The duck is flying
>>> skipper = Penguin()
>>> skipper.swim()
The penguin is swimming
>>> skipper.fly()
Traceback (most recent call last):
...
NotImplementedError: must be implemented in subclasses
В этом фрагменте кода класс Duck
работает должным образом. Между тем, класс Penguin
вызывает ошибку NotImplementedError
, когда вы вызываете метод .fly()
в одном из его экземпляров.
Ошибки утверждений: AssertionError
В Python есть функция, называемая утверждениями, которую вы можете определить с помощью оператора assert
. Утверждения позволяют вам устанавливать проверки работоспособности во время разработки кода. Утверждения позволяют вам проверить правильность вашего кода, проверив, остаются ли верными некоторые конкретные условия. Это пригодится во время тестирования и отладки кода.
Примечание. Чтобы глубже изучить оператор assert
, ознакомьтесь с учебным пособием Python Assert: «Отлаживайте и тестируйте свой код как профессионал».
Оператор утверждения имеет следующий синтаксис:
assert expression[, assertion_message]
Часть expression
— это условие, которое должно быть истинным, если в вашей программе нет ошибки. Если условие становится ложным, утверждение вызывает исключение AssertionError
и завершает выполнение вашей программы.
Утверждения полезны для написания тестовых примеров. Вы можете использовать их для проверки предположений, таких как предусловия и постусловия. Например, вы можете проверить, принадлежит ли аргумент заданному типу, вы можете проверить возвращаемое значение функции и т. д. Эти проверки могут помочь вам как можно скорее обнаружить ошибки при разработке программы.
Примечание. Не следует полагаться на утверждения для проверки предположений в рабочем коде, поскольку утверждения отключаются, когда ваш код выполняется в оптимизированном режиме с использованием Python -O
или -OO
параметры командной строки.
В качестве примера предположим, что вы находитесь на собеседовании по программированию Python. Вас просят реализовать функцию, решающую задачу FizzBuzz, где вы возвращаете "fizz"
для чисел, делящихся на три, "buzz"
для чисел, делящихся на пять, и "fizz buzz"
для чисел, кратных трем и пяти. Вы пишете функцию, подобную следующей:
def fizzbuzz(number):
if number % 3 == 0:
return "fizz"
elif number % 5 == 0:
return "buzz"
elif number % 15 == 0:
return "fizz buzz"
else:
return number
Эта функция, очевидно, охватывает все возможные сценарии. Однако вы решаете написать несколько базовых тестов, чтобы убедиться, что функция работает правильно. В конце вашего файла fizzbuzz.py
вы получите следующий код:
# ...
if __name__ == "__main__":
assert fizzbuzz(9) == "fizz"
assert fizzbuzz(10) == "buzz"
assert fizzbuzz(15) == "fizz buzz"
assert fizzbuzz(7) == 7
print("All tests pass")
В этом фрагменте кода вы добавили несколько быстрых утверждений, чтобы проверить, возвращает ли ваша функция правильную строку с разными входными значениями. Теперь вы можете запустить тест, запустив файл из командной строки:
$ python fizzbuzz.py
Traceback (most recent call last):
File ".../fizzbuzz.py", line 26, in <module>
assert fizzbuzz(15) == "fizz buzz"
^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
Ух ты! Вы получили исключение AssertionError
, что означает, что некоторые тесты не пройдены. Когда вы посмотрите на обратную трассировку исключений, вы заметите, что утверждение не выполняется, когда вы вызываете функцию с 15
в качестве аргумента. Это число делится на 3
и на 5
, поэтому текущий порядок условий неверен.
Необходимо переместить условие number % 15 == 0
на первую позицию:
def fizzbuzz(number):
if number % 15 == 0:
return "fizz buzz"
elif number % 3 == 0:
return "fizz"
elif number % 5 == 0:
return "buzz"
else:
return number
# ...
Условия сначала проверяют, делится ли входное число на 3
и 5
. Теперь запустите файл еще раз:
$ python fizzbuzz.py
All tests pass
Большой! Все ваши тесты пройдены. Функция корректно выполняет свою работу. В этом вся сила оператора assert
.
Наконец, обратите внимание, что не следует явно вызывать исключение AssertionError
в своем коде. Вместо этого вы должны позволить оператору assert
вызывать это исключение, когда условие утверждения не выполняется. Кроме того, не следует пытаться обрабатывать ошибки путем написания кода, который перехватывает исключение AssertionError
, поскольку утверждения можно отключить.
Исключения выхода интерпретатора
При написании кода Python вы столкнетесь с ситуациями, когда вам потребуется выйти или завершить работающее приложение или программу.
Например, если ошибка возникает во время работы приложения, вы можете решить полностью выйти из приложения с соответствующим статусом завершения, что может быть полезно в приложениях командной строки. Другой пример: вы тестируете какой-то код, который выполняется слишком долго или каким-то образом зависает. В этом случае вам нужен быстрый способ прекратить выполнение кода.
В Python есть следующие исключения, которые касаются таких ситуаций:
SystemExit
KeyboardInterrupt
В общем, ваш код не должен перехватывать или обрабатывать эти исключения. Это может помешать вам выйти из программы, сделать невозможным выход из кода или с помощью клавиатуры.
Примечание. Внезапное завершение программы с помощью SystemExit
или KeyboardInterrupt
может привести к нежелательным беспорядкам в вашей системе, например к оставшимся временным файлам и открытой сети. или подключения к базе данных.
Вы можете использовать модуль atexit
из стандартной библиотеки, чтобы управлять тем, как ваш код реагирует на эту практику. Этот модуль позволяет вам регистрировать и отменять регистрацию функций очистки, которые будут автоматически выполняться при обычном завершении интерпретатора Python.
В следующих разделах вы узнаете, когда эти исключения могут возникнуть в вашем коде Python. Вы также напишете пару примеров, иллюстрирующих их использование.
Выход из системы
Python вызывает исключение SystemExit
, когда вы вызываете функцию sys.exit()
в своем коде. Если вы не обработаете исключение, интерпретатор Python завершает работу, не печатая обратную трассировку исключения:
>>> import sys
>>> sys.exit(0)
$
В этом примере вы вызываете sys.exit()
, чтобы завершить работу интерпретатора Python. Этот вызов вернет вас в сеанс терминала.
На практике вы можете использовать SystemExit
непосредственно, когда вам нужно закрыть приложение. Например, предположим, что вы хотите создать минимальное приложение CLI (интерфейс командной строки), которое имитирует базовую функциональность команды Unix ls
, которая выводит содержимое заданного каталога.
В этой ситуации вы можете написать сценарий, подобный следующему:
import sys
from pathlib import Path
if (args_count := len(sys.argv)) > 2:
print(f"One argument expected, got {args_count - 1}")
raise SystemExit(2)
elif args_count < 2:
print("You must specify the target directory")
raise SystemExit(2)
target_dir = Path(sys.argv[1])
if not target_dir.is_dir():
print("The target directory doesn't exist")
raise SystemExit(1)
for entry in target_dir.iterdir():
print(entry.name)
Эта программа обрабатывает аргументы, предоставленные в командной строке, которые автоматически сохраняются в переменной sys.argv
. Первый элемент в sys.argv
— это имя программы. Вторым элементом должен быть целевой каталог.
Приложение должно принимать только один целевой каталог, поэтому значение переменной args_count
должно быть не более 2
. Если приложение получает более одного целевого каталога, вы печатаете сообщение об ошибке и вызываете исключение SystemExit
со статусом завершения 2
. Это указывает на то, что приложение закрылось после сбоя.
Ветка elif
проверяет, предоставил ли пользователь целевой каталог. Если это не так, вы печатаете пользователю сообщение об ошибке и выходите из приложения, вызывая исключение SystemExit
.
После проверки содержимого sys.argv
вы создаете объект pathlib.Path
для хранения пути к целевому каталогу. Если этот каталог не существует, вы сообщаете об этом пользователю и снова выходите из приложения, используя исключение SystemExit
. На этот раз статус завершения — 1
, что указывает на то, что приложение столкнулось с ошибкой во время выполнения. Наконец, цикл for
выводит содержимое каталога, по одной записи в строке.
В Unix-подобных системах, таких как Linux и macOS, вы можете запустить следующую команду, чтобы проверить работу сценария:
$ python ls.py
You must specify the target directory
$ echo $?
2
$ python ls.py /home /etc
One argument expected, got 2
$ echo $?
2
$ python ls.py .
hello.py
birds.py
grades.py
greeting.py
fizzbuzz.py
ls.py
mean.py
stack.py
square.py
rainbow.py
$ echo $?
0
Когда вы запускаете сценарий без аргументов, вы получаете сообщение о том, что вам необходимо указать целевой каталог. Когда вы используете команду echo
для проверки кода выхода, представленного $?
, вы увидите, что это 2
. Во втором примере вы предоставляете два целевых каталога. И снова приложение выходит из строя с сообщением об ошибке. Команда echo
также возвращает 2
.
Наконец, вы запускаете сценарий с текущим рабочим каталогом в качестве аргумента. В этом случае вы получите список файлов, находящихся в этом каталоге. Когда вы запускаете echo
, вы получаете статус завершения 0
, который сигнализирует об успешном выполнении приложения.
KeyboardInterrupt
Исключение KeyboardInterupt
несколько отличается от других исключений. Python вызывает это исключение, когда пользователь намеренно нажимает комбинацию клавиш Ctrl<span>+C во время выполнения программы. Это действие прерывает работающую программу.
Например, предположим, что вы работаете над фрагментом кода, включающим цикл. По ошибке код попадает в бесконечный цикл. В этой ситуации вам нужен быстрый способ прекратить выполнение кода. Вот когда вам пригодится комбинация клавиш Ctrl<span>+C.
Рассмотрим следующий игрушечный цикл:
>>> while True:
... print("Hello")
...
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Traceback (most recent call last):
...
KeyboardInterrupt
Этот цикл намеренно написан бесконечным. На практике могут возникнуть реальные циклы, которые из-за логической ошибки заканчиваются бесконечной итерацией. Если вы столкнулись с этой проблемой, вы можете нажать клавиши Ctrl<span>+C, чтобы прекратить выполнение кода. В результате Python выдаст исключение KeyboardInterrupt
.
Наконец, важно отметить, что вам не следует перехватывать это исключение в своем коде, поскольку это может помешать завершению работы интерпретатора.
Группы исключений
В Python 3.11 и более поздних версиях у вас будут классы ExceptionGroup
и BaseExceptionGroup
. Вы можете использовать их, когда вам нужно одновременно вызвать несколько несвязанных исключений. Разница между этими классами заключается в том, что BaseExceptionGroup
расширяет BaseException
, а ExceptionGroup
расширяет Exception
.
Примечание. Чтобы узнать больше о группах исключений, ознакомьтесь с руководством по предварительной версии Python 3.11: группы задач и исключений.
Вы можете использовать эти исключения, когда в асинхронной программе есть несколько одновременных задач, которые могут завершиться сбоем одновременно. Но в целом вы будете создавать ExceptionGroup
экономно.
При необходимости вы можете обрабатывать и создавать группы исключений. Вот как можно перехватить группу исключений с помощью соответствующего синтаксиса Exception*
:
>>> try:
... raise ExceptionGroup(
... "several exceptions",
... [
... ValueError("invalid value"),
... TypeError("invalid type"),
... KeyError("missing key"),
... ],
... )
... except* ValueError:
... print("Handling ValueError")
... except* TypeError:
... print("Handling TypeError")
... except* KeyError:
... print("Handling KeyError")
...
Handling ValueError
Handling TypeError
Handling KeyError
Синтаксис Exception*
позволяет перехватывать отдельные исключения в группе исключений. Таким образом, вы можете обрабатывать отдельные исключения определенными способами.
Заключение
Вы узнали о встроенных исключениях Python, которые обеспечивают быстрый и эффективный способ обработки ошибок и исключительных ситуаций в вашем коде. Теперь вы знаете наиболее часто используемые встроенные исключения, когда они появляются и как их использовать в коде обработки исключений. Вы также узнали, что эти исключения можно вызывать в своем коде.
В этом уроке вы:
- Узнали, что такое ошибки и исключения в Python.
- Понял, как Python организует встроенные исключения в иерархию классов.
- Изучены наиболее часто используемые встроенные исключения.
- Научились обрабатывать и вызывать встроенные исключения в вашем коде.
Эти знания помогут вам эффективно отлаживать код, поскольку каждое исключение имеет определенное значение и вариант использования. Вы также сможете эффективно обрабатывать и вызывать встроенные исключения в своем коде, что является отличным навыком для разработчика Python.