{ "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