{ "cells": [ { "cell_type": "markdown", "id": "4b201c2f", "metadata": {}, "source": [ "# Генерация исключений #" ] }, { "cell_type": "markdown", "id": "249ae766", "metadata": {}, "source": [ "На предыдущей лекции мы начали с вами свое знакомство с исключениями, и в этой лекции мы продолжим разбирать их работу и обсудим более подробно, как обрабатывать исключения, как получать доступ к объекту исключения, а также обсудим отдельные исключения вида `AssertionError` и вопросы производительности исключений." ] }, { "cell_type": "code", "execution_count": 1, "id": "5677dc5b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 No such file or directory\n" ] } ], "source": [ "try:\n", " with open(\"/file/not/found\") as f:\n", " content = f.read()\n", " \n", "except OSError as err:\n", " print(err.errno, err.strerror)" ] }, { "cell_type": "markdown", "id": "bba1230f", "metadata": {}, "source": [ "Для того чтобы получить доступ к объекту исключений, нам необходимо воспользоваться конструкцией `except as err`. В данном примере, если будет сгенерировано исключение `OSError`, то сам объект исключений будет связан с переменной `err` и эта переменная `err` будет доступна в блоке `except`." ] }, { "cell_type": "markdown", "id": "56767c12", "metadata": {}, "source": [ "У каждого объекта типа исключений есть свои свойства, например, `errno` и `srterror` — это строковое описание ошибки и код ошибки. При помощи этих атрибутов можно получать доступ и обрабатывать исключения нужным вам образом. Очень часто используется атрибут `args` для доступа к атрибутам исключения." ] }, { "cell_type": "code", "execution_count": 3, "id": "0c4d7d62", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "файл не существует /file/not/found\n" ] } ], "source": [ "import os.path\n", "\n", "filename = \"/file/not/found\"\n", "\n", "try:\n", " if not os.path.exists(filename):\n", " raise ValueError(\"файл не существует\", filename)\n", " \n", "except ValueError as err:\n", " message, filename = err.args[0], err.args[1]\n", " print(message, filename)" ] }, { "cell_type": "markdown", "id": "e3e20202", "metadata": {}, "source": [ "Предположим, мы проверили, что файл не существует, и сгенерировали исключение `ValueError`, передали туда строку и имя файла. Так вот, если мы в блоке `except` обратимся к объекту исключения, то у него будут доступен атрибут `args` — это список из наших параметров и можем делать с ними все, что захотим.\n", "\n", "Иногда нам может потребоваться стек вызовов при генерации исключения. Стек вызовов можно получить при помощи модуля `traceback` и вызвать метод `print_exc`. Давайте попробуем выполнить этот пример в консоли и посмотрим, что выведется на экран." ] }, { "cell_type": "code", "execution_count": 4, "id": "21569499", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"\", line 4, in \n", " with open(\"/file/not/found\") as f:\n", "FileNotFoundError: [Errno 2] No such file or directory: '/file/not/found'\n" ] } ], "source": [ "import traceback\n", "\n", "try:\n", " with open(\"/file/not/found\") as f:\n", " content = f.read()\n", "\n", "except OSError as err:\n", " trace = traceback.print_exc()\n", " print(trace)" ] }, { "cell_type": "markdown", "id": "06d47261", "metadata": {}, "source": [ "Итак, у нас получилось распечатать стек вызовов. Иногда стек вызовов может вам помочь при разбирательстве причин, при которых возникло исключение. \n", "\n", "Для того чтобы сгенерировать, собственно, исключение, вам необходима инструкция `raise`. Для генерации исключения мы должны написать `raise` и указать класс исключения. Также можно указывать не только класс, но и объект исключения, и указывать ему дополнительные какие-то свойства. Как я уже говорил, к этим свойствам потом можно будет обратится через объект исключения при помощи атрибута `args`." ] }, { "cell_type": "code", "execution_count": 5, "id": "b578c4de", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "введите число: w\n", "некорректное значение!\n" ] } ], "source": [ "try:\n", " raw = input(\"введите число: \")\n", " if not raw.isdigit():\n", " raise ValueError\n", " \n", "except ValueError:\n", " print(\"некорректное значение!\")" ] }, { "cell_type": "code", "execution_count": 6, "id": "28e93b13", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "введите число: w\n", "некорректное значение! ('плохое число', 'w')\n" ] } ], "source": [ "try:\n", " raw = input(\"введите число: \")\n", " if not raw.isdigit():\n", " raise ValueError(\"плохое число\", raw)\n", " \n", "except ValueError as err:\n", " print(\"некорректное значение!\", err)" ] }, { "cell_type": "markdown", "id": "531ad8cc", "metadata": {}, "source": [ "В данном случае мы проверяем, что пользователь ввел не число и если так действительно произошло, то генерируем исключение и затем в блоке `except` обрабатываем его.\n", "\n", "Иногда может случиться так, что вы пишете какую-то функцию и отлавливаете исключения, но вы не знаете, что делать дальше с этим исключением. Например, вы можете просто вывести какую-то информацию на экран и делегировать обработку этого исключения другим функциям — тем, который вызвали вашу. Для этого нужно использовать инструкцию `raise` без параметров." ] }, { "cell_type": "code", "execution_count": 7, "id": "0348d576", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "введите число: e\n", "некорректное значение! ('плохое число', 'e')\n" ] }, { "ename": "ValueError", "evalue": "('плохое число', 'e')", "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[0;32m 2\u001b[0m \u001b[0mraw\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0minput\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"введите число: \"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0misdigit\u001b[0m\u001b[1;33m(\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----> 4\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"плохое число\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: ('плохое число', 'e')" ] } ], "source": [ "try:\n", " raw = input(\"введите число: \")\n", " \n", " if not raw.isdigit():\n", " raise ValueError(\"плохое число\", raw)\n", "\n", "except ValueError as err:\n", " print(\"некорректное значение!\", err)\n", " # делегирование обработки исключения\n", " raise" ] }, { "cell_type": "markdown", "id": "75611c95", "metadata": {}, "source": [ "В данном случае все происходит тоже самое, как в предыдущем примере, генерируются исключения, на экран выводится надпись \"Некорректное значение\", и при помощи инструкции `raise` мы делегируем исключение выше. Если мы попробуем исполнить код, то интерпретатор просто покажет на стандартный вывод информации об этом исключении и прекратит работу нашей программы. Если таких мест, в которых исключения делегируются, очень много, то иногда не понятно по последнему исключению, где именно оно возникло и для этого может использоваться конструкция `raise from Exception`. Давайте посмотрим, как отработает наша программа." ] }, { "cell_type": "code", "execution_count": 8, "id": "fea7bab6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "введите число: d\n", "ошибка: плохое число d\n" ] }, { "ename": "TypeError", "evalue": "ошибка", "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[0;32m 4\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0misdigit\u001b[0m\u001b[1;33m(\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----> 5\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"плохое число\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: ('плохое число', 'd')", "\nThe above exception was the direct cause of the following exception:\n", "\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[0;32m 7\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"ошибка:\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m1\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----> 9\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"ошибка\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: ошибка" ] } ], "source": [ "try:\n", " raw = input(\"введите число: \")\n", " \n", " if not raw.isdigit():\n", " raise ValueError(\"плохое число\", raw)\n", " \n", "except ValueError as err:\n", " print(\"ошибка:\", err.args[0], err.args[1])\n", " raise TypeError(\"ошибка\") from err" ] }, { "cell_type": "markdown", "id": "00db7b28", "metadata": {}, "source": [ "Если мы сгенерируем одно исключение, отловим его в блоке `except`, и затем сгенерируем второе исключение `TypeError`, но укажем ему конструкцию `from err`. У нас получилось вызвать исключение и давайте внимательно посмотрим на `stack trace`, который произошел. Мы видим, что произошло исключение `ValueError` — это изначально которое мы сгенерировали. И дальше мы видим его `stack trace`, этого исключения. Дальше мы видим, что после этого исключения произошло второе исключение — `TypeError` и его `stack trace`. Таким образом, если мы будем использовать конструкцию `raise from Exception`, мы сможем отслеживать всю цепочку исключений, которая была сгенерирована." ] }, { "cell_type": "markdown", "id": "c41f831b", "metadata": {}, "source": [ "Говоря об исключениях, нельзя не затронуть инструкцию `assert`. Давайте поговорим, зачем она нужна. По умолчанию, если выполнить инструкцию `assert` с логическим выражением `True`, ничего не произойдет, но если попробовать выполнить инструкцию `assert` с логическим выражением, которое равно `False`, или \"ложь\", то будет сгенерировано исключение `AssertionError`." ] }, { "cell_type": "code", "execution_count": 9, "id": "64bc3761", "metadata": {}, "outputs": [], "source": [ "assert True" ] }, { "cell_type": "code", "execution_count": 10, "id": "4c4c07fe", "metadata": { "scrolled": true }, "outputs": [ { "ename": "AssertionError", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAssertionError\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;32massert\u001b[0m \u001b[1;36m1\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAssertionError\u001b[0m: " ] } ], "source": [ "assert 1 == 0" ] }, { "cell_type": "markdown", "id": "caeb02ab", "metadata": {}, "source": [ "Также мы можем передать некую дополнительную строку, которая будет потом передана в сам объект `AssertionError`." ] }, { "cell_type": "code", "execution_count": 11, "id": "381676b1", "metadata": {}, "outputs": [ { "ename": "AssertionError", "evalue": "1 не равен 0", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAssertionError\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;32massert\u001b[0m \u001b[1;36m1\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"1 не равен 0\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAssertionError\u001b[0m: 1 не равен 0" ] } ], "source": [ "assert 1 == 0, \"1 не равен 0\"" ] }, { "cell_type": "markdown", "id": "e799aaab", "metadata": {}, "source": [ "Давайте разберем кейсы, в каких случаях может использоваться `AssertionError` и инструкция `assert`. \n", "\n", "Предположим, у нас есть функция, она называется `get_user_by_id`, она нам ищет некоего пользователя по численному идентификатору. Мы должны убедиться в том, что нам действительно передали число, что мы работаем с целым числом. Это можно сделать при помощи `assert` и функции `isinstance`, то есть мы проверяем, является ли входной параметр `id` целым числом. Если это не так, то будет сгенерирована `AssertionError`." ] }, { "cell_type": "code", "execution_count": 12, "id": "33871e62", "metadata": {}, "outputs": [ { "ename": "AssertionError", "evalue": "id должен быть целым числом", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAssertionError\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 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m\"__main__\"\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[0mget_user_by_id\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"foo\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36mget_user_by_id\u001b[1;34m(id)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_user_by_id\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mid\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[1;32massert\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mid\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mint\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"id должен быть целым числом\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"выполняем поиск\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m\"__main__\"\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mAssertionError\u001b[0m: id должен быть целым числом" ] } ], "source": [ "def get_user_by_id(id):\n", " assert isinstance(id, int), \"id должен быть целым числом\"\n", " print(\"выполняем поиск\")\n", "\n", "if __name__ == \"__main__\":\n", " get_user_by_id(\"foo\")" ] }, { "cell_type": "markdown", "id": "5196f5e1", "metadata": {}, "source": [ "Итак, у нас сгенерировалось исключение `AssertionError`. Дело в том, что такие исключения не нужно обрабатывать — эти исключения, они предназначены, скорее, для программистов. То есть при написании наших программ на этапе разработки мы должны видеть, что мы делаем что-то не так, то есть мы передали в функцию некорректное значение, и видим `AssertionError` — наша программа завершила свою работу. В таком случае мы должны как-то реагировать и изменять код нашей программы. Не нужно, например, обрабатывать пользовательский ввод и пытаться обработать исключение `AssetionError` блоком `try except`.\n", "\n", "Если у нас таких мест будет очень много, то это затронет и производительность нашей программы. Оказывается, можно отключить все инструкции `assert` при помощи флага `−O`. Тогда `AssertionError`, инструкция не будет сгенерирована, потому что `assert` вызван не будет. Этим как раз отличаются эти исключения от обычных пользовательских исключений и исключений стандартной библиотеки. Давайте попробуем сделать это. Запустим с флагом `−O` нашу программу." ] }, { "cell_type": "code", "execution_count": 15, "id": "310942f0", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "выполняем поиск\n" ] } ], "source": [ "! python -O ex.py" ] }, { "cell_type": "markdown", "id": "765e8f49", "metadata": {}, "source": [ "Итак, мы видим, что наша функция отработала, исключение `AssertionError` не было сгенерировано.\n", "\n", "Давайте еще поговорим немного о производительности исключений. Несмотря на то, что механизм обработки исключений очень удобный и его очень хорошо использовать в своих программах, тем не менее этот механизм не бесплатен. \n", "\n", "Рассмотрим пример. У нас есть цикл, и в этом цикле мы обращаемся к несуществующему ключу словаря и каждый раз при этом обращении у нас генерируется исключение `KeyError`. Попробуем при помощи `timeit` замерить, сколько это займет — мы получим некую условную единицу, тысячу операций мы смогли выполнить." ] }, { "cell_type": "code", "execution_count": 16, "id": "6f59e6df", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "454 µs ± 9.97 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" ] } ], "source": [ "%%timeit\n", "my_dict = {\"foo\": 1}\n", "\n", "for _ in range(1000):\n", " try:\n", " my_dict[\"bar\"]\n", " \n", " except KeyError:\n", " pass" ] }, { "cell_type": "markdown", "id": "469eb46f", "metadata": {}, "source": [ "Если мы попробуем реализовать ту же самую задачу, но без использования исключений, например, при помощи конструкции `in`, то есть мы проверяем, что у нас ключик `bar` находится в словаре, и только в этом случае тогда мы обращаемся по ключу к этому словарю. Давайте опять попробуем посмотреть на результаты работы `timeit` для этого примера — и мы видим, что результат отличается на порядок, а иногда он может отличаться и на несколько порядков." ] }, { "cell_type": "code", "execution_count": 17, "id": "4c40a7cb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "67.3 µs ± 4.65 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" ] } ], "source": [ "%%timeit\n", "my_dict = {\"foo\": 1}\n", "\n", "for _ in range(1000):\n", " if \"bar\" in my_dict:\n", " _ = my_dict[\"bar\"]" ] }, { "cell_type": "markdown", "id": "85daa3f9", "metadata": {}, "source": [ "То есть хочу обратить ваше внимание на то, что исключения, особенно если они у вас генерируются в программе очень часто, возможно следует изменить код вашей программы и сделать так, чтобы она работала без исключений, потому, что это очень сильно влияет на производительность вашей итоговой Python программы.\n", "\n", "Итак, мы обсудили вопросы, которые касаются исключений, поговорили про доступ к объекту исключений, о том как генерируются исключения при помощи `raise`, также обсудили специальные исключения типа `AssertionError`, поговорили о вопросах производительности и нам осталось поговорить о работе с собственными исключениями. В целом работа с собственными исключениями никак не отличается от работы с исключениями стандартной библиотеки и мы попробуем в следующей лекции разобрать работу с собственными исключениями на конкретном примере работы с библиотекой `requests`." ] } ], "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 }