{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Классы и исключения" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этой лекции рассказывается о том, что такое классы и как с помощью них определять собственные сложные типы данных. Отдельно рассматриваются специальные классы, называемые исключениями, которые используются для обработки ошибок, возникающих в процессе выполнения программы. Также мы представим концепцию и основные принципы объектно-ориентированного программирования, которое пришло на смену процедурному, рассмотренному в предыдущей лекции." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Содержание лекции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* [Классы и объекты](#Классы-и-объекты)\n", " * [Конструктор](#Конструктор)\n", " * [Статические атрибуты](#Статические-атрибуты)\n", " * [Пространства имен](#Пространства-имен)\n", "* [Объектно-ориентированное программирование](#Объектно-ориентированное-программирование)\n", " * [Инкапсуляция](#Инкапсуляция)\n", " * [Наследование и полиморфизм](#Наследование-и-полиморфизм)\n", " * [Композиция](#Композиция)\n", "* [Исключения](#Исключения)\n", "* [Продвинутые приемы программирования](#Продвинутые-приемы-программирования)\n", " * [Декораторы](#Декораторы)\n", " * [Перегрузка операций](#Перегрузка-операций)\n", " * [Множественное наследование](#Множественное-наследование)\n", " * [Абстрактные базовые классы](#Абстрактные-базовые-классы)\n", "* [Вопросы для самоконтроля](#Вопросы-для-самоконтроля)\n", "* [Задание](#Задание)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Классы и объекты" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Класс** - это сложный составной тип данных, состоящий из произвольного количества атрибутов и методов. **Атрибутами** (иногда называемыми также полями) класса называются его внутренние переменные, а **методами** - функции, которые выполняют различные операции над ними. Атрибуты и методы определяют структуру, общую для всех конкретных представителей класса, которых называют **объектами** или **экземплярами**. Вообще, понятия класса и его объекта связаны друг с другом так же, как, например, понятия \"автомобиль\" (класс, имеющие такие атрибуты, как тип кузова и мощность) и \"toyota corolla\" (конкретный представитель, со значениями атрибутов \"седан\" и \"124\")." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Классы создаются (или по другому - определяются) в Python с помощью инструкции `class`, имеющей следующий синтаксис:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n",
"def class_name(base_classes):\n",
" instruction1\n",
" ...\n",
" instructionN\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Имя класса *class_name* должно быть [допустимым идентификатором](04_Data_Types.ipynb#Допустимые-идентификаторы) в языке Python. Если имя состоит из нескольких слов, то они записываются слитно без пробелов, при этом каждое слово пишется с заглавной буквы (такой стиль называется [CamelCase](https://ru.wikipedia.org/wiki/CamelCase)), например, `Circle`, `MyClass`, `SomeLongNameForClass`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Предложение *base_classes* содержит список базовых классов, разделенных запятой (подробнее об этом рассказывает в разделе, посвященном наследованию)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Инструкции *instructionK* могут быть любыми корректными инструкциями языка Python, однако в большинстве случаев используются иснтрукции присваивания `=` и определения функции `def`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"class MyClass:\n",
" def print_hello(self):\n",
" print('hello, object:', id(self))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"В примере выше мы создали наш первый очень простой класс, в котором описан только один метод `print_hello`. Методы вызываются для конкретного объекта и имеют как минимум один обязательный параметр, который по общепринятому соглашению называется `self`. Этот параметр инициализируется самим интерпретатором, и в качестве значения получает ссылку на тот объект, для которого вызван метод."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Итак, для того, чтобы вызывать метод `print_hello`, нам для начала нужно создать объект класса `MyClass`. Делается это очень просто:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"raise exception_type(argument_list)\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Здесь *exception_type* представляет класс исключения, а *argument_list* список (возможно пустой) аргументов, которые передаются в его конструктор."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Давайте напишем функцию `divide`, которая будет генерировать исключение, если в качестве знаменателя указан 0. Если обратиться к справочному руководству Python, то можно обнаружить в нем исключение `ZeroDivisionError`, которое является как раз тем, что нам нужно."
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"ename": "ZeroDivisionError",
"evalue": "Can't divide 5 by 0!",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m\n",
"try:\n",
" try_code_block\n",
"except exception_list_1 as variable_1:\n",
" except_code_block_1\n",
"...\n",
"except exception_list_N as variableN:\n",
" except_code_block_N\n",
"else:\n",
" else_code_block\n",
"finally:\n",
" finally_code_block\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Давайте разберем подробно компоненты этой конструкции:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* *try_code_block* содержит инструкции, для которых мы хотим выполнять перехват исключений\n",
"* *except_code_block_K* содержит блок кода, являющийся обработчиком исключений, указанных в соответствующем *exception_list_K*\n",
"* *else_code_block* содержит инструкции, которые должны быть выполнены, если никаких исключений при выполнении *try_code_block* не возникло\n",
"* *finally_code_block* содержит инструкции, которые должны быть выполнены в любом случае, причем в самом конце (либо после *try_code_block*, если не было исключений, либо после *except_code_block_K*, если произошло одно из *exception_list_K* исключений, либо после того, как было сгенерировано исключение, но не был найден подходящий обработчик, и интерпретатор продолжил его поиск в вызывающей функции или методе)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Корректный вариант представленной конструкции обязательно должен содержать предложение `try` и хотя бы одно предложение `except` или `finally`, все остальное можно не указывать, если в этом нет необходимости."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Список перехватываемых исключений *exception_list_K* может отсутствовать в предложении `except`. В этом случае, перехватываться будет исключение любого типа."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Часть *as* предложения `except` также является необязательной. Она используется для того, чтобы при перехвате исключения создать новую переменную с именем *variableK*, и присвоить ей ссылку на объект исключения. Тогда в обработчике можно будет обращаться к данным, хранящимся в исключении. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Начнем рассматривать примеры обработки исключений, начиная с простых и двигаясь к все более сложным. Для начала реализуем функцию деления в старом процедурном стиле:"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [],
"source": [
"def divide_old(a, b):\n",
" if b == 0:\n",
" return None\n",
" return a / b"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"С помощью такой функции мы можем написать такую программу:"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3.0\n",
"program is finished\n"
]
}
],
"source": [
"a = 10\n",
"b = 5\n",
"\n",
"result = divide_old(a, b)\n",
"result += 1\n",
"print(result)\n",
"print('program is finished')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"С этим кодом все в порядке ровно до тех пор, пока переменная `b` не окажется равной 0:"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "unsupported operand type(s) for +=: 'NoneType' and 'int'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m