{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "(exceptions)=\n", "# Исключения\n", "\n", "В `Python` существует минимум два типа ошибок: синтаксические ошибки и [исключения](https://docs.python.org/3/tutorial/errors.html). Синтаксические ошибки чаще возникают у изучающих `python` программистов из-за нарушения синтаксиса. Но попытка запуска корректной с точки зрения синтаксиса программы может привести к возникновению ошибки. Ошибки, обнаруженные во время исполнения программы, называются исключениями (`exception`) и не всегда ведут к падению программы: их можно обрабатывать и восстанавливать нормальный поток исполнения программы.\n", "\n", "Тем не менее большинство ошибок программой не обрабатывается и их возникновение приводит к выводу сообщения об ошибке. В качестве примера попробуем поделить на ноль. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_13252/2354412189.py\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;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mZeroDivisionError\u001b[0m: division by zero" ] } ], "source": [ "1/0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видимо, появилось сообщение об ошибке. Последняя строка этого сообщения, говорит, что конкретно произошло. \n", "\n", "- Исключения бывают разных типов и конкретный тип брошенного исключения печатается до двоеточия. В данном случае это [ZeroDivisionError](https://docs.python.org/3/library/exceptions.html#ZeroDivisionError).\n", "\n", "- После типа ошибки выводится дополнительная информация, которая может зависеть от типа ошибки и причины её возникновения. \n", "- Всё, что идёт до последней строки, демонстрирует в каком контексте возникла ошибка в виде трассировочных данных. Более наглядное представление о трассировочной информации даёт пример, когда ошибка возникает внутри нескольких функций." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_13252/4200539898.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[0mf\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[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7\u001b[1;33m \u001b[0mg\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[0m", "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_13252/4200539898.py\u001b[0m in \u001b[0;36mg\u001b[1;34m()\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\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[0mf\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[0m\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[0mg\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~\\AppData\\Local\\Temp/ipykernel_13252/4200539898.py\u001b[0m in \u001b[0;36mf\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mf\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----> 2\u001b[1;33m \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[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\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[0;32m 5\u001b[0m \u001b[0mf\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;31mZeroDivisionError\u001b[0m: division by zero" ] } ], "source": [ "def f():\n", " 1/0\n", "\n", "def g():\n", " f()\n", "\n", "g()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этот раз сообщение об ошибке гораздо длиннее, потому что ошибка возникла не на самом верхнем уровне, а на уровне функции. \n", "\n", "## Встроенные исключения\n", "\n", "В `python` есть приличное количество встроенных исключений, которые интерпретатор бросает при обнаружении ошибки во время исполнения. Например, если попытаться обратиться к необъявленной переменной (несуществующему имени), то возникнет ошибка [NameError](https://docs.python.org/3/library/exceptions.html#NameError). Обращение к неправильному атрибуту вызовет ошибку [AttributeError](https://docs.python.org/3/library/exceptions.html#AttributeError), обращение за пределы списка --- [IndexError](https://docs.python.org/3/library/exceptions.html#IndexError), поиск в словаре по несуществующему ключу --- [KeyError](https://docs.python.org/3/library/exceptions.html#KeyError) и т.д. \n", "\n", "В ряде ситуаций `python` возбуждает исключение не в результате ошибки, а в служебных целях. Например, как обсуждалось ранее, функции работающие с итерируемыми объектами ожидают исключение [StopIteration](https://docs.python.org/3/library/exceptions.html#StopIteration), т.е. появление такого исключения --- не является исключительной ситуацией, связанной с логической ошибкой в коде, а лишь механизм, которым итерируемый объект сообщает клиентскому коду, что элементы в нем исчерпались.\n", "\n", "В `python` всё является объектом, в том числе и исключения. При этом у каждого исключения есть свой тип, примеры которых уже обсуждалось выше: `NameError`, `AttributeError`, `StopIteration` и т.д. --- это всё тип исключения. Обнаружение ошибки приводит к тому, что создаётся экземпляр исключения соответствующего типа и это исключение начинает распространяться. При этом все встроенные типы исключений находятся в иерархии, с которой можно ознакомиться по [ссылке](https://docs.python.org/3/library/exceptions.html#exception-hierarchy). \n", "\n", "Так, на самом верху располагается базовый класс [BaseException](https://docs.python.org/3/library/exceptions.html#BaseException) от которого наследуется все остальные типы исключений. На втором этаже располагаются исключения связанные с завершением исполнения программы (например, [SystemExit](https://docs.python.org/3/library/exceptions.html#BaseException) возникает при вызове функции [sys.exit()](https://docs.python.org/3/library/sys.html#sys.exit), [KeyboardInterrupt](https://docs.python.org/3/library/exceptions.html#KeyboardInterrupt) возникает, если пользователь нажимает прерывающую комбинацию клавиш во время работы приложения), а так же класс [Exception](https://docs.python.org/3/library/exceptions.html#Exception), который и является базовым классом для всех исключений не связанных с завершением программы.\n", "\n", "С полным списком встроенных исключений можно ознакомиться на [странице официальной документации](https://docs.python.org/3/library/exceptions.html).\n", "\n", "## Возбуждение исключений. `raise`\n", "\n", "Многие исключения бросаются интерпретатором `python` при обнаружении ошибки или в служебных целях. Для этого используется ключевое слово `raise` справа от которого указывается экземпляр исключения. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "ename": "Exception", "evalue": "Моё первое исключение!", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mException\u001b[0m Traceback (most recent call last)", "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_13252/2359356075.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\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[0m", "\u001b[1;31mException\u001b[0m: Моё первое исключение!" ] } ], "source": [ "raise Exception(\"Моё первое исключение!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Когда исключение возбужденно, оно начинает распространяться, т.е. нормальное исполнение программы прекращается. Так, например, покинуть функцию можно не только используя ключевое слово `return`, но и ключевым словом `raise`. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "До исключения!\n" ] }, { "ename": "Exception", "evalue": "Исключение!", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mException\u001b[0m Traceback (most recent call last)", "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_13252/2985915162.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 4\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 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[0mf\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[0m", "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_13252/2985915162.py\u001b[0m in \u001b[0;36mf\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mf\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[0;32m 2\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[1;32m----> 3\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\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[0m\u001b[0;32m 4\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 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mException\u001b[0m: Исключение!" ] } ], "source": [ "def f():\n", " print(\"До исключения!\")\n", " raise Exception(\"Исключение!\")\n", " print(\"После исключения!\")\n", "\n", "f()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Видим, что инструкция `print(\"До исключения!\")` исполнилась, а инструкция `print(\"После исключения\")` --- нет, т.к. сразу после возбуждения исключения нормальный поток исполнения программы прекратился и исключение начало распространяться. Исключение распространяется наверх по стеку вызовов, пока не встретится блок, обрабатывающий это исключение. Если такой блок так и не встречается, то выводится сообщение об ошибке и программа падает (или может продолжить ожидание инструкций от пользователя в интерактивном режиме, что происходит, например, в `jupyter` ноутбуках)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "\n", "## Обработка исключений. \n", "\n", "Обработка ошибок в `python` осуществляется с помощью инструкции `try`, которая может встречаться в блоках двух видов:\n", "- `try/except`;\n", "- `try/finally`; \n", "\n", "## Блок `try/except`\n", "\n", "С помощью блока `try/except` можно обрабатывать конкретные типы исключений. В самой простой своей форме он выглядит примерно следующим образом.\n", "```python\n", "try:\n", " инструкции\n", "except ТипИсключения:\n", " инструкции\n", "```\n", "\n", "После ключевого слова `try` помещаются инструкции, исполнение которых может привести к возникновению исключения. Инструкции в блоке `except` выполняются только, если а) вовремя выполнения инструкций в блоке `try` возникла ошибка и б) её тип совпал с указанным типом. \n", "\n", "В качестве примера опять поделим на ноль, но в этот раз поместим это деление в блок `try/except` и обработаем исключение типа `ZeroDivisionError`." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Перехвачена ошибка деления на ноль!\n" ] } ], "source": [ "try:\n", " 1/0\n", " print(\"После попытки деления.\")\n", "except ZeroDivisionError:\n", " print(\"Перехвачена ошибка деления на ноль!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Видим, что \n", "1. сообщения об ошибке с типом ошибки и трассировочной информацией не появилось, т.к. ошибка была обработана;\n", "2. вместо этого выполнилась инструкция `print` в блоке `except ZeroDivisionError`; \n", "3. инструкция `print` в блоке `try` после деления на ноль не выполнилась, т.к. ошибка возникла раньше и начала распространяться.\n", "\n", "Когда исключение возникает, оно начинает распространяться вверх по стеку вызовов функций, а нормальное исполнение программы прекращается. Как только встречается первый блок `except` совпадающего типа, распространение исключения прекращается и выполняется код в этом блоке.\n", "\n", "Приведем несколько примеров, чтобы продемонстрировать этот механизм.\n", "\n", "Следующий пример демонстрирует, что исключение продолжает распространяться, пока не встретит правильный блок `except`." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ошибка перехвачена внешним блоком try/except.\n", "Инструкция после обработки исключения.\n" ] } ], "source": [ "try:\n", " try:\n", " 1/0\n", " except NameError:\n", " print(\"Ошибка перехвачена внутренним блоком try/except.\")\n", "except ZeroDivisionError:\n", " print(\"Ошибка перехвачена внешним блоком try/except.\")\n", "\n", "print(\"Инструкция после обработки исключения.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь исключение `ZeroDivisionError` возникло во внутреннем блоке `try/except`, но тип возникшего исключения не совпал с `NameError`. В итоге внутренний блок `try/except` ошибку не обработало и она продолжила распространяться, в результате чего попала во внешний блок `try/except`, который обрабатывает ошибки типа `ZeroDivisionError`, что привело прекращению распространения ошибки и к исполнению инструкции `print` во внешнем блоке. Далее программа продолжает исполняться в нормальном режиме. \n", "\n", "Следующий пример демонстрирует, что исключение прекращает распространяться, как только встретит правильный блок `except`." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ошибка перехвачена внутренним блоком except.\n", "Инструкция после обработки исключения.\n" ] } ], "source": [ "try:\n", " try:\n", " 1/0\n", " except ZeroDivisionError:\n", " print(\"Ошибка перехвачена внутренним блоком except.\")\n", "except ZeroDivisionError:\n", " print(\"Ошибка перехвачена внешним блоком except.\")\n", "\n", "print(\"Инструкция после обработки исключения.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь внутренний блок `try/except` обрабатывает правильный тип исключения. Возникшее исключение прекращает распространяться в этом внутреннем блоке `try/except` и выполняется инструкция `print` во внутреннем блоке `except`. Т.к. исполнение инструкций после внешнего ключевого слова `try` проходит без ошибок (ошибка возникает, но внешний блок её не замечает, т.к. она обрабатывается внутренним блоком `try/except`), то внешний блок `except` игнорируется. \n", "\n", "Блоков `except` может быть несколько в одном блоке `try/except`, что позволяет по-разному реагировать на ошибки разных типов. " ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Перехвачена ошибка ZeroDivisionError\n" ] } ], "source": [ "try:\n", " 1/0\n", "except NameError:\n", " print(\"Перехвачена ошибка NameError\")\n", "except ZeroDivisionError:\n", " print(\"Перехвачена ошибка ZeroDivisionError\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Интерпретатор читает блоки `except` сверху вниз и останавливается на первом, который обрабатывает исключение правильного типа. Т.к. типы исключений располагаются в иерархии между собой, то необходимо придерживаться следующего правила.\n", "\n", "```{tip}\n", "Сначала всегда обрабатывайте самые специфичные исключения, а уже потом более общие.\n", "```\n", "\n", "Если не придерживаться этого правила, то специфичные блоки `except` будут недостижимы. Продемонстрируем это на примере, воспользовавшись тем, что `Exception` является базовым классом почти для всех остальных встроенных исключений, а значит исключения этого типа являются чуть ли не самыми общими." ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Перехвачено неожиданное исключение!\n" ] } ], "source": [ "try:\n", " 1/0\n", "except Exception:\n", " print(\"Перехвачено неожиданное исключение!\")\n", "except ZeroDivisionError:\n", " print(\"Перехвачено исключение ZeroDivisionError!\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь в блоке `try` возникает исключение `ZeroDivisionError` и начинает распространяться. В блоке `except Exception` проверка на тип успешно проходит (иерархия между ними выглядит так: `Exception` $\\to$ `ArithmeticError` $\\to$ `ZeroDivisionError`), а значит вызывается инструкции в соответствующем блоке, а до блока `except ZeroDivisionError` дело так и не доходит: ошибка обработана ранее.\n", "\n", "Правильнее в данном случае было бы поменять эти блоки местами." ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Перехвачено исключение ZeroDivisionError!\n" ] } ], "source": [ "try:\n", " 1/0\n", "except ZeroDivisionError:\n", " print(\"Перехвачено исключение ZeroDivisionError!\")\n", "except Exception:\n", " print(\"Перехвачено неожиданное исключение!\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Дополнительные возможности `except`\n", "\n", "После `except` можно указать целевую переменную, чтобы получить доступ к объекту исключения синтаксисом\n", "```python\n", "except ТипИсключения as цель:\n", " операции над цель\n", "```\n", "Из некоторых объектов исключений можно получить дополнительную информацию об контексте, при которым они возбудились. У всех стандартных исключений преобразование к строке определенно таким образом, чтобы выводить сообщение, указанное при создании." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Сообщение исключения!\n" ] } ], "source": [ "try:\n", " raise Exception(\"Сообщение исключения!\")\n", "except Exception as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "После ключевого слова `except` необязательно должен быть указан только один тип исключения: можно указать кортеж." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Возникло исключение ZeroDivisionError или NameError!\n" ] } ], "source": [ "try:\n", " 1/0\n", "except (ZeroDivisionError, NameError):\n", " print(\"Возникло исключение ZeroDivisionError или NameError!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Так же, в самом конце блока `try/except` можно добавить блок `else`, инструкции в котором исполняться, только если блок `try` завершится без ошибок. Чтобы продемонстрировать это, определим функцию `divide`, которая будет пытаться делить первый аргумент на второй и возвращать результат деления, если деление произошло успешно, и возвращать бесконечность, если второй параметр равен нулю." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "def divide(a, b):\n", " try:\n", " result = a / b\n", " except ZeroDivisionError:\n", " print(\"Попытка деления на ноль.\")\n", " return float(\"inf\")\n", " else:\n", " print(\"Деление произошло успешно.\")\n", " return result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Блок `else` в этой функции исполнится, только если в блоке `try` деление произойдет успешно, т.е. если не возникнет ошибки деления на ноль." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Деление произошло успешно.\n", "3.0\n", "Попытка деления на ноль.\n", "inf\n" ] } ], "source": [ "print(divide(42, 14))\n", "print(divide(42, 0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Блок `try/finally`\n", "\n", "Синтаксис блока `try/except` выглядит следующим образом.\n", "```python\n", "try:\n", " инструкции\n", "finally:\n", " инструкции\n", "```\n", "\n", "Инструкции в блоке `finally` исполнятся в не зависимости от того, возникнет ли ошибка в блоке `try` или нет. Если ошибка все же возникает, то выполняется код в блоке `finally` и ошибка продолжает распространяться.\n", "\n", "Этот блок часто называют обработчик очистки: часто в нем размещают инструкции, которые выполняют чистку (например, освобождение ресурсов). Это позволяет гарантировать, что инструкции в этом блоке исполнятся в любом случае. \n", "\n", "Например, раньше часто встречались конструкции следующего вида.\n", "\n", "```python\n", "f = open(some_file, \"w\")\n", "try:\n", " do_something_with_file(f)\n", "finally:\n", " f.close()\n", "```\n", "\n", "Т.е. операции над открытым файлом производились в блоке `try/finally`, что гарантировало, что файл будет закрыт, даже если возникнет исключение. Сегодня гораздо удобнее использовать контекстные менеджеры `with` для таких целей. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Блок `try/except/finally`\n", "\n", "Если встречается блок обработки ошибки, в котором встречаются и `except` и `finally`, то его можно представить в виде вложения блока `try/except` внутрь блока `try/finally`. Иными словами, следующие две конструкции ведут себя одинаково. \n", "\n", "````{panels}\n", "```{code} python\n", "try:\n", " инструкции\n", "except SomeException:\n", " инструкции\n", "else:\n", " инструкции\n", "finally:\n", " инструкции\n", "```\n", "---\n", "```{code} python\n", "try:\n", " try:\n", " инструкции\n", " except SomeException:\n", " инструкции\n", " else:\n", " инструкции\n", "finally:\n", " инструкции\n", "```\n", "````\n", "\n", "Для демонстрации работы блока такого вида приведем пример из документации. " ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [], "source": [ "def divide(x, y):\n", " try:\n", " result = x / y\n", " except ZeroDivisionError:\n", " print(\"Попытка деления на ноль!\")\n", " else:\n", " print(f\"Результат деления {result}\")\n", " finally:\n", " print(\"Выполнение блока 'finally'.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функция `divide` пытается поделить `x` на `y`. \n", "\n", "Если деление происходит успешно (`else`), то выводится результат." ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Результат деления 2.0\n", "Выполнение блока 'finally'.\n" ] } ], "source": [ "divide(2, 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если возникает ошибка деления на ноль, то она перехватывается и выводится соответствующее сообщение. " ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Попытка деления на ноль!\n", "Выполнение блока 'finally'.\n" ] } ], "source": [ "divide(2, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если возникает ошибка другого рода, то её распространение продолжается." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "executing finally clause\n" ] }, { "ename": "TypeError", "evalue": "unsupported operand type(s) for /: 'str' and 'str'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_13252/1759864827.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mdivide\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"2\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"1\"\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~\\AppData\\Local\\Temp/ipykernel_13252/3246861353.py\u001b[0m in \u001b[0;36mdivide\u001b[1;34m(x, y)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdivide\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\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[0;32m 2\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mresult\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mx\u001b[0m \u001b[1;33m/\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mZeroDivisionError\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"division by zero!\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for /: 'str' and 'str'" ] } ], "source": [ "divide(\"2\", \"1\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Блок `finally` выполняется в любом случае." ] } ], "metadata": { "interpreter": { "hash": "cd49b4596bae9c980ff74fdf93e8fe80e447435ae307c062fad6c4f9ef2eb47f" }, "kernelspec": { "display_name": "Python 3.8.10 ('venv': venv)", "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.10" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }