Exceptions Python
Введение
Если в коде есть ошибка, которую видит интерпретатор поднимается исключение, создается так называемый 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 | |
Теория Программирования |
РЕКЛАМА от Яндекса. Может быть недоступна в вашем регионе
Конец рекламы. Если там пусто считайте это рекламой моей телеги