{ "cells": [ { "cell_type": "markdown", "id": "adeac7e8", "metadata": {}, "source": [ "# Классы исключений и их обработка #" ] }, { "cell_type": "markdown", "id": "e85bd50b", "metadata": {}, "source": [ "В предыдущих лекциях мы несколько раз упоминали про исключения в Python. Сегодня мы как раз обсудим то, как устроены исключения в Python. Мы поговорим про генерацию исключений, что при этом происходит, обсудим типы исключений, а также рассмотрим, как обрабатывать исключения в Python.\n", "\n", "Для начала давайте попробуем вызвать исключения и посмотрим, что при этом произойдет. Для этого нам потребуется консоль. Давайте попробуем написать простенькую программу на Python. Пусть это будет файл `zero.py`. Попробуем вывести строчку `1/0`. Давайте запустим нашу программу." ] }, { "cell_type": "code", "execution_count": 1, "id": "3a0fab6c", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"zero.py\", line 1, in \n", " 1/0\n", "ZeroDivisionError: division by zero\n" ] } ], "source": [ "! python zero.py" ] }, { "cell_type": "markdown", "id": "546ec448", "metadata": {}, "source": [ "Итак, мы видим, что произошло. При делении на 0 возникло исключение, и при возникновении исключения на стандартный вывод печатается информация о его типе, дополнительная информация о исключении, а также стек вызовов.\n", "\n", "Давайте посмотрим, что у нас произошло. Мы видим, на экран напечаталось - тип исключения — `ZeroDivisionError`, дополнительная информация и сам стек. Стек у нас пока небольшой, и для того чтобы посмотреть на стек настоящей программы, давайте посмотрим дополнительный пример." ] }, { "cell_type": "markdown", "id": "4e1a1ee8", "metadata": {}, "source": [ "Пусть у нас есть программа, которая считает количество строк в исходном файле, который подали на вход. Выглядит она нам не очень интересно как, то есть есть некая функция `main`, в функции `main` вызывается другая функция. Эта функция вызывает функцию `wc` с переданным файлом и печатает на экран некую строчку с именем файла и количеством строк в нем. А функция `wc` открывает этот файл и итератором проходится по всем строкам в этом файле.\n", "\n", "```python\n", "import sys\n", "\n", "def wc(filename):\n", " count = 0\n", " \n", " with open(filename) as f:\n", " for _ in f:\n", " count += 1\n", " \n", " return count\n", "\n", "def process_file(filename):\n", " count = wc(filename)\n", " \n", " print(f\"file: {filename} has {count} lines\")\n", "\n", "def _main():\n", " process_file(sys.argv[1])\n", " \n", "if __name__ == \"__main__\":\n", " _main()\n", "```" ] }, { "cell_type": "code", "execution_count": 9, "id": "8f7658a8", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"wc.py\", line 21, in \n", " _main()\n", " File \"wc.py\", line 18, in _main\n", " process_file(sys.argv[1])\n", " File \"wc.py\", line 13, in process_file\n", " count = wc(filename)\n", " File \"wc.py\", line 6, in wc\n", " with open(filename) as f:\n", "FileNotFoundError: [Errno 2] No such file or directory: '/etc/test'\n" ] } ], "source": [ "! python wc.py /etc/test" ] }, { "cell_type": "markdown", "id": "7e6043d2", "metadata": {}, "source": [ "Давайте попробуем вызвать данную программу, передадим ей либо несуществующий файл, либо файл, недоступный для чтения, и посмотрим, что при этом произойдет. Итак, у нас есть наша программа. Давайте попробуем ей передать файл, которого нет. Это файл `/etc/test`. Итак, как видим, сгенерировалось исключение.\n", "\n", "При этом Python остановил свою работу, и на экране мы видим тип исключения — это `FileNotFoundError`, а также дополнительную информацию - код ошибки, текстовые сообщения, что файла `/etc/test` нет. Также теперь мы видим стек вызовов, теперь он у нас побольше. Давайте распечатаем текст самой программы и немножко разберемся, что же значит этот стек вызовов.\n", "\n", "Давайте посмотрим, что на строке 6 фукнции `wc` была вызвана функция `open`. Давайте еще раз сгенерируем наше исключение. Итак, мы сгенерировали исключение, и посмотрим на текст нашей программы. Итак, мы видим, что в строке 6 нашей программы была вызвана функция `open`. Именно это привело к генерации исключения. Давайте дальше пройдемся по стеку вызовов и раскрутим его наверх. Мы видим, что наша функция `wc` была вызвана на строке 13, вот видим ее. Это было сделано функцией `process_file`. Если мы будем раскручивать стек дальше, то мы сможем отследить всю последовательность вызовов функции, которая привела к исключению. Таким образом, это нам очень может сильно помочь, если мы будем разбираться с чужими исключениями, которые вдруг внезапно возникли в программе и не были обработаны программистом.\n", "\n", "Какие типы исключений бывают? В Python бывают по большому счету два типа исключений.\n", "\n", "Первый — это исключения из стандартной библиотеки в Python. Они генерируются, собственно, самой библиотекой. То есть когда мы вызываем функцию стандартной библиотеки, например, мы видели, как функция `open` сгенерировала исключение `PermissionError`.\n", "\n", "А также второй тип исключений — это пользовательские исключения. Они могут быть сгенерированы и обработаны самим программистом при написании программ на Python.\n", "\n", "Давайте посмотрим на иерархию исключений в стандартной библиотеке Python. Все исключения наследуются от базового класса `BaseException`. Существуют несколько системных исключений, например, `SystemExit` — это исключение генерируется, если мы вызвали функцию `OSExit`. `KeyboardInterrupt` — это исключение генерируется, если мы нажали сочетание клавиш `Ctrl + C` и так далее." ] }, { "cell_type": "markdown", "id": "0ffb082b", "metadata": {}, "source": [ "```\n", "BaseException\n", "+-- SystemExit\n", "+-- KeyboardInterrupt\n", "+-- GeneratorExit\n", "+-- Exception\n", " +-- StopIteration\n", " +-- AssertionError\n", " +-- AttributeError\n", " +-- LookupError\n", " +-- IndexError\n", " +-- KeyError\n", " +-- OSError\n", " +-- SystemError\n", " +-- TypeError\n", " +-- ValueError\n", "```" ] }, { "cell_type": "markdown", "id": "1dd418b8", "metadata": {}, "source": [ "Все остальные исключения генерируется от базового класса `Exception`. Именно от этого класса нужно будет порождать и свои исключения, чем мы и займемся дальше.\n", "\n", "Давайте посмотрим и обсудим некоторые исключения из базы библиотеки, такие как, например, `AttributeError`, `IndexError`, `KeyError`, `TypeError`, и попробуем их вызвать. Для этого нам снова потребуется консоль. Давайте запустим интерпретатор. Объявим простенький класс. Пусть это будет класс, который ничего не делает." ] }, { "cell_type": "code", "execution_count": 10, "id": "b09aeca0", "metadata": {}, "outputs": [], "source": [ "class MyClass:\n", " pass" ] }, { "cell_type": "markdown", "id": "9f2183bb", "metadata": {}, "source": [ "Давайте создадим объект этого класса и попробуем обратиться к атрибуту `foo`." ] }, { "cell_type": "code", "execution_count": 11, "id": "6ac92428", "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'MyClass' object has no attribute 'foo'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mobj\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mMyClass\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mobj\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfoo\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m: 'MyClass' object has no attribute 'foo'" ] } ], "source": [ "obj = MyClass()\n", "obj.foo" ] }, { "cell_type": "markdown", "id": "e2657a83", "metadata": {}, "source": [ "Как мы видим, сгенерировалось исключение `AttributeError`.\n", "\n", "Давайте попробуем объявить словарик. Пусть в нем будет один элемент. " ] }, { "cell_type": "code", "execution_count": 12, "id": "e57ffe19", "metadata": {}, "outputs": [], "source": [ "d = {\"foo\": 1}" ] }, { "cell_type": "markdown", "id": "7535f072", "metadata": {}, "source": [ "Попробуем обратиться к несуществующему ключику." ] }, { "cell_type": "code", "execution_count": 13, "id": "69060ba8", "metadata": {}, "outputs": [ { "ename": "KeyError", "evalue": "'bar'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0md\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"bar\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mKeyError\u001b[0m: 'bar'" ] } ], "source": [ "d[\"bar\"]" ] }, { "cell_type": "markdown", "id": "87cb9673", "metadata": {}, "source": [ "Итак, у нас получилось сгенерировать исключение `KeyError`.\n", "\n", "Если бы мы объявили список из двух элементов и обратились, например, к 10-му элементу, у нас бы сгенерировался `IndexError`." ] }, { "cell_type": "code", "execution_count": 14, "id": "e134d942", "metadata": {}, "outputs": [ { "ename": "IndexError", "evalue": "list index out of range", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mIndexError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0ml\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0ml\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mIndexError\u001b[0m: list index out of range" ] } ], "source": [ "l = [1, 2]\n", "l[10]" ] }, { "cell_type": "markdown", "id": "aeb59421", "metadata": {}, "source": [ "Также если мы, например, попробовали преобразовать к целому числу строчку, мы бы получили `ValueError`." ] }, { "cell_type": "code", "execution_count": 15, "id": "cd6a04fc", "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "invalid literal for int() with base 10: 'fdf'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"fdf\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mValueError\u001b[0m: invalid literal for int() with base 10: 'fdf'" ] } ], "source": [ "int(\"fdf\")" ] }, { "cell_type": "markdown", "id": "51be3abc", "metadata": {}, "source": [ "Или если бы попробовали сложить число и строку, получили бы `TypeError`." ] }, { "cell_type": "code", "execution_count": 16, "id": "f6ccfd3b", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unsupported operand type(s) for +: 'int' and 'str'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;36m1\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;34m\"1\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'int' and 'str'" ] } ], "source": [ "1 + \"1\"" ] }, { "cell_type": "markdown", "id": "6ea521e0", "metadata": {}, "source": [ "Все эти исключения — из стандартной библиотеки. Вам при работе очень часто придется сталкиваться с ними, и теперь вы знаете, как они выглядят, и при каких обстоятельствах они могут быть сгенерированы.\n", "\n", "Если исключение сгенерировано, то, как я уже говорил, Python-интерпретатор остановит свою работу и на экран будет выведен стек вызовов и информация о типе исключений.\n", "\n", "И нам это не всегда хочется, чтобы такое поведение было по умолчанию, и поэтому нужно как-то обработать сгенерированные исключения. Обработать его можно при помощи блока `try except`. То есть у нас есть код, который потенциально может генерировать исключения, мы этот код обрамляем в блок `try except`, и тем самым при генерации исключений управление будет передано в блок `except`." ] }, { "cell_type": "code", "execution_count": 17, "id": "a88352fe", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error\n" ] } ], "source": [ "try:\n", " 1 / 0\n", "\n", "except:\n", " print(\"Error\")" ] }, { "cell_type": "markdown", "id": "ed95ab0c", "metadata": {}, "source": [ "Таким образом можно отловить все исключения, которые генерируются в блоке `try except`.\n", "\n", "Если мы в блоке `except` укажем исключение, например в данном случае `Exception`, то мы будем отлавливать исключения всех типов, у которых класс `Exception` является родителем." ] }, { "cell_type": "code", "execution_count": 18, "id": "c48a05fb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error\n" ] } ], "source": [ "try:\n", " 1 / 0\n", "\n", "except Exception:\n", " print(\"Error\")" ] }, { "cell_type": "markdown", "id": "4427af89", "metadata": {}, "source": [ "В целом неправильно ждать любые исключения, и это может привести к непредвиденным сюрпризам работы вашей программы.\n", "\n", "Давайте рассмотрим следующий пример. Мы в бесконечном цикле просим пользователя ввести число, преобразовываем его строку к числу целому." ] }, { "cell_type": "markdown", "id": "b8d34e47", "metadata": {}, "source": [ "```python\n", "while True:\n", " try:\n", " raw = input(\"Input number: \")\n", " number = int(raw)\n", " break\n", " \n", " except:\n", " print(\"not number\")\n", "```" ] }, { "cell_type": "markdown", "id": "0f185a81", "metadata": {}, "source": [ "В данном случае может возникнуть исключение `ValueError`. Однако, мы в своей программе отлавливаем все исключения. Давайте попробуем ее исполнить и посмотрим, как она работает. Так, нас просят ввести число. Давайте введем. Нас снова просят ввести число. Давайте, например, попросим наш интерпретатор завершить свою работу и нажмем `Ctrl + C`.\n", "\n", "Как мы видим, у нас это не получилось сделать, то есть наша программа стала вести себя непредвиденным образом. Она нас снова опять заставляет ввести число. Это как раз говорит о том, что нужно обрабатывать конкретные исключения, и в данном случае правильным вариантом данной программы была бы обработка исключений `ValueError`." ] }, { "cell_type": "markdown", "id": "eef3c8bd", "metadata": {}, "source": [ "```python\n", "while True:\n", " try:\n", " raw = input(\"Input number: \")\n", " number = int(raw)\n", " break\n", " \n", " except ValueError:\n", " print(\"not number\")\n", "```" ] }, { "cell_type": "markdown", "id": "d7444dfb", "metadata": {}, "source": [ "Если мы попробуем снова запустить данную программу и нажмем `Ctrl + C`. Давайте введем сначала неправильное число, нажмем `Ctrl + C`, то возникнет исключение и его никто не обработает, и наша программа завершит свою работу. Поэтому не следует обрабатывать все исключения и оставлять пустым блок `except`. Имейте это в виду.\n", "\n", "Также у блока `try except` может быть блок `else`. Блок `else` вызывается в том случае, если никакого исключения не произошло." ] }, { "cell_type": "markdown", "id": "28cb2cbe", "metadata": {}, "source": [ "```python\n", "while True:\n", " try:\n", " raw = input(\"Input number: \")\n", " number = int(raw)\n", " break\n", " \n", " except ValueError:\n", " print(\"not number\")\n", " \n", " else:\n", " break\n", "```" ] }, { "cell_type": "markdown", "id": "545ef10a", "metadata": {}, "source": [ "Если нам нужно обработать несколько исключений, мы можем использовать несколько блоков `except` и указать разные классы для обработки исключения. Причем в каждом блоке `except` может быть свой собственный обработчик." ] }, { "cell_type": "markdown", "id": "2a06fab7", "metadata": {}, "source": [ "```python\n", "while True:\n", " try:\n", " raw = input(\"Input number: \")\n", " number = int(raw)\n", " break\n", " \n", " except ValueError:\n", " print(\"not number\")\n", " \n", " except KeyboardInterrupt:\n", " print(\"exit\")\n", " break\n", "```" ] }, { "cell_type": "markdown", "id": "dd9d3b79", "metadata": {}, "source": [ "Например, если мы в данном примере ввели некорректное число, то мы еще раз продолжим работу нашего цикла. В противном случае, если мы нажали `Ctrl + C`, то мы прекратим цикл. Именно таким образом можно обработать несколько исключений." ] }, { "cell_type": "markdown", "id": "2db929d8", "metadata": {}, "source": [ "Если обработчик исключений выглядит одинаково, то несколько исключений можно передать в виде списка в блок `except` и также обработать сгенерированные исключения." ] }, { "cell_type": "markdown", "id": "8185d03b", "metadata": {}, "source": [ "```python\n", "while True:\n", " try:\n", " raw = input(\"Input number: \")\n", " number = int(raw)\n", " total_count /= number\n", " break\n", " \n", " except (ValueError, ZeroDivisionError):\n", " print(\"not number\")\n", "```" ] }, { "cell_type": "markdown", "id": "076e56ef", "metadata": {}, "source": [ "В данном случае мы ожидаем два исключения, например, что пользователь ввел некорректное число, либо если он ввел 0, то данная программа сгенерирует `ZeroDivisionError`, и мы его отловим.\n", "\n", "Мы уже с вами обсуждали классы и наследования в классах, и вот у `exception`'ов есть своя иерархия, и сделана она неспроста. Также при помощи родительского класса можно обрабатывать несколько исключений. Давайте, например, посмотрим на иерархию классов `LookupError`." ] }, { "cell_type": "markdown", "id": "8ce76d09", "metadata": {}, "source": [ "```\n", "+-- LookupError\n", " +-- IndexError\n", " +-- KeyError\n", "```" ] }, { "cell_type": "markdown", "id": "047d3573", "metadata": {}, "source": [ "Этот класс является родительским для исключений `IndexError` и `KeyError`. Мы можем это проверить при помощи известных нам функций `issubclass`. Рассмотрим следующий пример." ] }, { "cell_type": "code", "execution_count": 2, "id": "42250fda", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "issubclass(KeyError, LookupError)" ] }, { "cell_type": "code", "execution_count": 3, "id": "b891412f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "issubclass(IndexError, LookupError)" ] }, { "cell_type": "markdown", "id": "a7b4d748", "metadata": {}, "source": [ "У нас есть некая структура данных, это наша база данных, которая хранит по цветам надписи на футболках. Нам необходимо получать доступ к этим надписям по цветам.\n", "\n", "Пользователя просим ввести цвет, просим ввести номер по порядку, а затем обращаемся к нашей структуре данных по ключу, а затем по индексу. И если пользователь введет, например, некорректный цвет, то будет сгенерировано исключение `KeyError`, а если пользователь введет недопустимый индекс, то будет вызвано исключение `IndexError`. Все эти исключения мы можем обработать при помощи базового класса `LookupError`. Иногда это очень удобно и требуется для как раз обработки пользовательских исключений. " ] }, { "cell_type": "code", "execution_count": 5, "id": "be5fda81", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "введите цвет: red\n", "введите номер по порядку: 1\n", "вы выбрали: flower\n" ] } ], "source": [ "database = {\n", " \"red\": [\"fox\", \"flower\"],\n", " \"green\": [\"peace\", \"M\", \"python\"]\n", "}\n", "\n", "try:\n", " color = input(\"введите цвет: \")\n", " number = input(\"введите номер по порядку: \")\n", " label = database[color][int(number)]\n", " print(\"вы выбрали:\", label)\n", "\n", "except LookupError:\n", " # except (IndexError, KeyError):\n", " print(\"Объект не найден\")" ] }, { "cell_type": "markdown", "id": "a0060f21", "metadata": {}, "source": [ "Также у исключений есть дополнительный блок `finally`. Рассмотрим проблему. Например, мы открываем файл, читаем строки, обрабатываем как-то эти строки, и в процессе работы нашей программы возникает исключение, которое мы не ждем, например. В таком случае файл закрыт не будет. У нас эти открытые файловые дескрипторы могут накапливаться, что, в принципе, не хорошо. Таким же образом могут накапливаться открытые сокеты, или не освобождаться память, всё что угодно.\n", "\n", "Для контроля таких ситуаций существуют, во-первых, контекстные менеджеры, а во-вторых, можно использовать блок `finally` в исключениях. Выглядит это таким образом, как представлено на слайде." ] }, { "cell_type": "markdown", "id": "0a0ce1a0", "metadata": {}, "source": [ "```python\n", "f = open(\"/etc/hosts\")\n", "\n", "try:\n", " for line in f:\n", " print(line.rstrip(\"\\n\"))\n", " 1 / 0\n", " \n", "except OSError:\n", " print(\"ошибка\")\n", " \n", "finally:\n", " f.close()\n", "```" ] }, { "cell_type": "markdown", "id": "991a0c2a", "metadata": {}, "source": [ "Мы пишем блок `finally` и вызываем метод `close` для нашего объекта `f`. В данном случае блок `finally` будет выполнен в любом случае. Возникло исключение или не возникло — блок `finally` будет выполнен.\n", "\n", "Итак, мы поговорили с вами про исключения, посмотрели на то, как они выглядят, как выглядит `stack trace`, обсудили, как ведет себя Python при генерации исключений. Также посмотрели на то, какие типы исключений бывают - это пользовательские исключения и исключения стандартной библиотеки. Также рассмотрели иерархию исключений в стандартной библиотеке и поговорили про их обработку. Мы продолжим обсуждать то, как устроены исключения, на следующей лекции." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 5 }