Exceptions Python

Содержание
Введение
Пример с базовым Exception
Два исключения
except Error as e:: Печать текста ошибки
else
finally
raise
Пример 2
Пример 3
Исключения, которые не нужно обрабатывать
Список исключений
Разбор примеров: IndexError, ValueError, KeyError
Похожие статьи

Введение

Если в коде есть ошибка, которую видит интерпретатор поднимается исключение, создается так называемый Exception Object, выполнение останавливается, в терминале показывается Traceback.

В английском языке используется словосочетание Raise Exception

Исключение, которое не было предусмотрено разработчиком называется необработанным (Unhandled Exception)

Такое поведение не всегда является оптимальным. Не все ошибки дожны останавливать работу кода. Возможно, где-то разработчик ожидает появление ошибок и их можно обработать по-другому.

try и except нужны прежде всего для того, чтобы код правильно реагировал на возможные ошибки и продолжал выполняться там, где появление ошибки некритично.

Исключение, которое предусмотрено в коде называется обработанным (Handled)

Блок try except имеет следующий синтаксис

try: pass except Exception: pass else: pass finally: pass

В этой статье я создал файл try_except.py куда копирую код из примеров.

Пример

Попробуем открыть несуществующий файл и воспользоваться базовым Exception

try: f = open('missing.txt') except Exception: print('ERR: File not found')

python try_except.py

ERR: No missing.txt file found

Ошибка поймана, видно наше сообщение а не Traceback

Проверим, что когда файл существует всё хорошо

try: f = open('existing.txt') except Exception: print('ERR: File not found')

python try_except.py

Пустота означает успех

Два исключения

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

try: f = open('existing.txt') x = bad_value except Exception: print('ERR: File not found')

python try_except.py

ERR: File not found

Файл открылся, но так как в следующей строке ошибка - в терминале появилось вводящее в заблуждение сообщение. Проблема не в том, что "File not found" а в том, что bad_value нигде не определёно.

Избежать сбивающих с толку сообщений можно указав тип ожидаемой ошибки. В данном примере это FileNotFoundError

try: # expected exception f = open('existing.txt') # unexpected exception should result in Traceback x = bad_value except FileNotFoundError: print('ERR: File not found')

python try_except.py

Traceback (most recent call last): File "/home/andrei/python/try_except2.py", line 5, in <module> x = bad_value NameError: name 'bad_value' is not defined

Вторая ошибка не поймана поэтому показан Traceback

Поймать обе ошибки можно добавив второй Exception

try: # expected exception should be caught by FileNotFoundError f = open('missing.txt') # unexpected exception should be caught by Exception x = bad_value except FileNotFoundError: print('ERR: File not found') except Exception: print('ERR: Something unexpected went wrong')

python try_except.py

ERR: File not found
ERR: Something unexpected went wrong

Печать текста ошибки

Вместо своего текста можно выводить текст ошибки. Попробуем с существующим файлом - должна быть одна пойманная ошибка.

try: # expected exception should be caught by FileNotFoundError f = open('existing.txt') # unexpected exception should be caught by Exception x = bad_value except FileNotFoundError as e: print(e) except Exception as e: print(e)

python try_except.py

name 'bad_value' is not defined

Теперь попытаемся открыть несуществующий файл - должно быть две пойманные ошибки.

try: # expected exception should be caught by FileNotFoundError f = open('missing.txt') # unexpected exception should be caught by Exception x = bad_value except FileNotFoundError as e: print(e) except Exception as e: print(e)

python try_except.py

name 'bad_value' is not defined
[Errno 2] No such file or directory: 'missing.txt'

РЕКЛАМА от Яндекса. Может быть недоступна в вашем регионе

Конец рекламы. Если там пусто считайте это рекламой моей телеги

else

Блок else будет выполнен если исключений не будет поймано.

Попробуем открыть существующий файл existing.txt в котором есть строка www.heihei.ru

try: f = open('existing.txt') except FileNotFoundError as e: print(e) except Exception as e: print(e) else: print(f.read()) f.close()

python try_except.py

www.heihei.ru

Если попробовать открыть несуществующий файл missing.txt то исключение обрабатывается, а код из блока else не выполняется.

[Errno 2] No such file or directory: 'missing.txt'

finally

Блок finally будет выполнен независимо от того, поймано исключение или нет

try: f = open('existing.txt') except FileNotFoundError as e: print(e) except Exception as e: print(e) else: print(f.read()) f.close() finally: print("Finally!")

www.heihei.ru Finally!

А если попытаться открыть несуществующий missing.txt

[Errno 2] No such file or directory: 'missing.txt' Finally!

Когда нужно применять finally:

Рассмотрим скрипт, который вносит какие-то изменения в систему. Затем он пытается что-то сделать. В конце возвращает систему в исходное состояние.

Если ошибка случится в середине скрипта - он уже не сможет вернуть систему в исходное состояние.

Но если вынести возврат к исходному состоянию в блок finally он сработает даже при ошибке в предыдущем блоке.

import os def make_at(path, dir_name): original_path = os.getcwd() os.chdir(path) os.mkdir(dir_name) os.chdir(original_path)

Этот скрипт не вернётся в исходную директорию при ошибке в os.mkdir(dir_name)

А у скрипта ниже такой проблемы нет

def make_at(path, dir_name): original_path = os.getcwd() os.chdir(path) try: os.mkdir(dir_name) finally: os.chdir(original_path)

Не лишнима будет добавить обработку и вывод исключения

import os import sys def make_at(path, dir_name): original_path = os.getcwd() os.chdir(path) try: os.mkdir(dir_name) except OSError as e: print(e, file=sys.stderr) raise finally: os.chdir(original_path)

По умолчанию print() выводит в sys.stdout, но в случае ислючений логичнее выводить в sys.stderr

raise

Можно вызывать исключения вручную в любом месте кода с помощью raise.

try: f = open('outdated.txt') if f.name == 'outdated.txt': raise Exception except FileNotFoundError as e: print(e) except Exception as e: print('File is outdated!') else: print(f.read()) f.close() finally: print("Finally!")

python try_except.py

File is outdated! Finally!

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

Для этого достаточно вызвать raise без аргументов - поднимется текущее исключение.

Пример 2

Рассмотрим функцию, которая принимает числа прописью и возвращает цифрами

DIGIT_MAP = { 'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5', 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9', } def convert(s): number = '' for token in s: number += DIGIT_MAP[token] x = int(number) return x

python

>>> from exc1 import convert >>> convert("one three three seven".split()) 1337

Теперь передадим аргумент, который не предусмотрен в словаре

>>> convert("something unseen".split()) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/python/exc1.py", line 17, in convert number &plu= DIGIT_MAP[token] KeyError: 'something'

KeyError - это тип Exception объекта. Полный список можно изучить в конце статьи.

Исключение прошло следующий путь:

REPL convert() DIGIT_MAP("something") KeyError

Обработать это исключение можно внеся изменения в функцию convert

convert(s): try: number = '' for token in s: number += DIGIT_MAP[token] x = int(number) print("Conversion succeeded! x = ", x) except KeyError: print("Conversion failed!") x = -1 return x

>>> from exc1 import convert >>> convert("one nine six one".split()) Conversion succeeded! x = 1961 1961 >>> convert("something unseen".split()) Conversion failed! -1

Эта обработка не спасает если передать int вместо итерируемого объекта

>>> convert(2022) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/python/exc1.py", line 17, in convert for token in s: TypeError: 'int' object is not iterable

Нужно добавить обработку TypeError

except KeyError: print("Conversion failed!") x = -1 except TypeError: print("Conversion failed!") x = -1 return x

>>> from exc1 import convert >>> convert("2022".split()) Conversion failed! -1

Избавимся от повторов, удалив принты, объединив два исключения в кортеж и вынесем присваивание значения x из try блока.

Также добавим докстринг с описанием функции.

def convert(s): """Convert a string to an integer.""" x = -1 try: number = '' for token in s: number += DIGIT_MAP[token] x = int(number) except (KeyError, TypeError): pass return x

>>> from exc4 import convert >>> convert("one nine six one".split()) 1961 >>> convert("bad nine six one".split()) -1 >>> convert(2022) -1

Ошибки обрабатываются, но без принтов, процесс не очень информативен.

Грамотно показать текст сообщений об ошибках можно импортировав sys и изменив функцию

import sys DIGIT_MAP = { 'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5', 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9', } def convert(s): """Convert a string to an integer.""" try: number = '' for token in s: number += DIGIT_MAP[token] return(int(number)) except (KeyError, TypeError) as e: print(f"Conversion error: {e!r}", file=sys.stderr) return -1

>>> from exc1 import convert >>> convert(2022) Conversion error: TypeError("'int' object is not iterable") -1 >>> convert("one nine six one".split()) 1961 >>> convert("bad nine six one".split()) Conversion error: KeyError('bad')

Ошибки обрабатываются и их текст виден в терминале.

С помощью !r выводится repr() ошибки

raise вместо кода ошибки

В предыдущем примере мы полагались на возвращение числа -1 в качестве кода ошибки.

Добавим к коду примера функцию string_log() и поработаем с ней

def string_log(s): v = convert(s) return log(v)

>>> from exc1 import string_log >>> string_log("one two eight".split()) 4.852030263919617 >>> string_log("bad one two".split()) Conversion error: KeyError('bad') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/exc1.py", line 32, in string_log return log(v) ValueError: math domain error

convert() вернул -1 а string_log попробовал его обработать и не смог.

Можно заменить return -1 на raise. Это считается более правильным подходом в Python

def convert(s): """Convert a string to an integer.""" try: number = '' for token in s: number += DIGIT_MAP[token] return(int(number)) except (KeyError, TypeError) as e: print(f"Conversion error: {e!r}", file=sys.stderr) raise

>>> from exc7 import string_log >>> string_log("one zero".split()) 2.302585092994046 >>> string_log("bad one two".split()) Conversion error: KeyError('bad') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/exc7.py", line 31, in string_log v = convert(s) File "/home/andrei/exc7.py", line 23, in convert number += DIGIT_MAP[token] KeyError: 'bad'

Пример 3

Рассмотрим алгоритм по поиску квадратного корня

def sqrt(x): """Compute square roots using the method of Heron of Alexandria. Args: x: The number for which the square root is to be computed. Returns: The square root of x. """ guess = x i = 0 while guess * guess != x and i < 20: guess = (guess + x / guess) / 2.0 i += 1 return guess def main(): print(sqrt(9)) print(sqrt(2)) if __name__ == '__main__': main()

python sqrt_ex.py

3.0 1.414213562373095

При попытке вычислить корень от -1 получим ошибку

def main(): print(sqrt(9)) print(sqrt(2)) print(sqrt(-1))

python sqrt_ex.py

3.0 1.414213562373095 Traceback (most recent call last): File "/home/andrei/sqrt_ex.py", line 26, in <module> main() File "/home/andrei/sqrt_ex.py", line 23, in main print(sqrt(-1)) File "/home/andrei/sqrt_ex.py", line 16, in sqrt guess = (guess + x / guess) / 2.0 ZeroDivisionError: float division by zero

В строке

guess = (guess + x / guess) / 2.0

Происходит деление на ноль

Обработать можно следующим образом:

def main(): try: print(sqrt(9)) print(sqrt(2)) print(sqrt(-1)) except ZeroDivisionError: print("Cannot compute square root " "of a negative number.") print("Program execution continues " "normally here.")

Обратите внимание на то, что в try помещены все вызовы функции

python sqrt_ex.py

3.0 1.414213562373095 Cannot compute square root of a negative number. Program execution continues normally here.

Если пытаться делить на ноль несколько раз - поднимется одно исключение и всё что находится в блоке try после выполняться не будет

def main(): try: print(sqrt(9)) print(sqrt(-1)) print(sqrt(2)) print(sqrt(-1))

python sqrt_ex.py

3.0 Cannot compute square root of a negative number. Program execution continues normally here.

Каждую попытку вычислить корень из -1 придётся обрабатывать отдельно. Это кажется неудобным, но в этом и заключается смысл - каждое место где вы ждёте ислючение нужно помещать в свой try except блок.

Можно обработать исключение так:

try: while guess * guess != x and i < 20: guess = (guess + x / guess) / 2.0 i += 1 except ZeroDivisionError: raise ValueError() return guess def main(): print(sqrt(9)) print(sqrt(-1))

python sqrt_ex.py

3.0 Traceback (most recent call last): File "/home/andrei/sqrt_ex3.py", line 17, in sqrt guess = (guess + x / guess) / 2.0 ZeroDivisionError: float division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/andrei/sqrt_ex3.py", line 30, in <module> main() File "/home/andrei/sqrt_ex3.py", line 25, in main print(sqrt(-1)) File "/home/andrei/sqrt_ex3.py", line 20, in sqrt raise ValueError() ValueError

Гораздо логичнее поднимать исключение сразу при получении аргумента

def sqrt(x): """Compute square roots using the method of Heron of Alexandria. Args: x: The number for which the square root is to be computed. Returns: The square root of x. Raises: ValueError: If x is negative """ if x < 0: raise ValueError( "Cannot compute square root of " f"negative number {x}") guess = x i = 0 while guess * guess != x and i < 20: guess = (guess + x / guess) / 2.0 i += 1 return guess def main(): print(sqrt(9)) print(sqrt(-1)) print(sqrt(2)) print(sqrt(-1)) if __name__ == '__main__': main()

python sqrt_ex.py

3.0 Traceback (most recent call last): File "/home/avorotyn/python/lessons/pluralsight/core_python_getting_started/chapter8/sqrt_ex4.py", line 35, in <module> main() File "/home/avorotyn/python/lessons/pluralsight/core_python_getting_started/chapter8/sqrt_ex4.py", line 30, in main print(sqrt(-1)) File "/home/avorotyn/python/lessons/pluralsight/core_python_getting_started/chapter8/sqrt_ex4.py", line 17, in sqrt raise ValueError( ValueError: Cannot compute square root of negative number -1

Пока получилось не очень - виден Traceback

Убрать Traceback можно добавив обработку ValueError в вызов функций

import sys def sqrt(x): """Compute square roots using the method of Heron of Alexandria. Args: x: The number for which the square root is to be computed. Returns: The square root of x. Raises: ValueError: If x is negative """ if x < 0: raise ValueError( "Cannot compute square root of " f"negative number {x}") guess = x i = 0 while guess * guess != x and i < 20: guess = (guess + x / guess) / 2.0 i += 1 return guess def main(): try: print(sqrt(9)) print(sqrt(2)) print(sqrt(-1)) print("This is never printed") except ValueError as e: print(e, file=sys.stderr) print("Program execution continues normally here.") if __name__ == '__main__': main()

python sqrt_ex.py

3.0 1.414213562373095 Cannot compute square root of negative number -1 Program execution continues normally here.

Исключения, которые не нужно обрабатывать

IndentationError, SyntaxError, NameError нужно исправлять в коде а не пытаться обработать.

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

Список исключений

Список встроенных в Python исключений

Существуют следующие типы объектов Exception

BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- 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 +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- EncodingWarning +-- ResourceWarning

IndexError

Объекты, которые поддерживают протокол Sequence должны поднимать исключение IndexError при использовании несуществующего индекса.

IndexError как и KeyError относится к ошибкам поиска LookupError

Пример

>>> a = [0, 1, 2] >>> a[3]

Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range

ValueError

ValueError поднимается когда объект правильного типа, но содержит неправильное значение

>>> int("text")

Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: 'text'

KeyError

KeyError поднимается когда поиск по ключам не даёт результата

>>> sites = dict(urn=1, heihei=2, eth1=3) >>> sites["topbicycle"]

Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'topbicycle'

TypeError

TypeError поднимается когда для успешного выполнения операции нужен объект определённого типа, а предоставлен другой тип.

Пример из статьи str()

pi = 3.1415 text = "Pi is approximately " + pi

python str_ex.py

Traceback (most recent call last): File "str_ex.py", line 3, in <module> text = "Pi is approximately " + pi TypeError: can only concatenate str (not "float") to str

Следующий пример возникает если написать метод __int__(), который возвращает не int

class MyNumber: def __init__(self, number: int) -> None: self.number = number def __int__(self) -> int: return float(self.number) print(int(MyNumber(2)))

Traceback (most recent call last): File "C:\AutoTest\int_demo.py", line 9, in <module> print(int(MyNumber(2))) ^^^^^^^^^^^^^^^^ TypeError: __int__ returned non-int (type float)

# typing_extensions.py # line 879 # Our version of runtime-checkable protocols is faster on Python 3.7-3.11 if sys.version_info >= (3, 12): SupportsInt = typing.SupportsInt SupportsFloat = typing.SupportsFloat SupportsComplex = typing.SupportsComplex SupportsBytes = typing.SupportsBytes SupportsIndex = typing.SupportsIndex SupportsAbs = typing.SupportsAbs SupportsRound = typing.SupportsRound else: @runtime_checkable class SupportsInt(Protocol): """An ABC with one abstract method __int__.""" __slots__ = () @abc.abstractmethod def __int__(self) -> int: pass

SyntaxError

SyntaxError поднимается когда допущена ошибка в синтаксисе языка, например, использован несуществующий оператор.

Python 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> >>> 0 <> 0 File "<stdin>", line 1 0 <> 0 ^ SyntaxError: invalid syntax

Пример из статьи __future__

IndentationError

IndentationError поднимается когда допущена ошибка в количестве пробелов во вложенных блоках кода.

for i in range(1, 4): print(i)

Код без пробела перед print(i) работать не будет. Получится ошибка

File "/home/andrei/python/for_loop.py", line 2 print(i) ^ IndentationError: expected an indented block

По стандарту PEP 8 нужно поставить четыре пробела перед print(i)

Похожие статьи
C
C++
Go
Groovy
Java
JavaScript
PHP
Python
Ruby
.NET/C#
Thrift
Теория Программирования

РЕКЛАМА от Яндекса. Может быть недоступна в вашем регионе

Конец рекламы. Если там пусто считайте это рекламой моей телеги

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: