Лекция 4
Классы, объекты, наследование
Постановка задачи
Ваш персонаж - Steve.
У него: имя, здоровье, координаты на карте, инвентарь.
Как это запрограммировать?
Первая попытка
Один игрок - отлично работает.
А если их 1000?
А ещё мобы вокруг. И деревни. И животные. Что-то пошло не так.
Получше, но не идеально
Уже лучше. Но игрок должен ходить, ломать блоки, есть еду.
Каждый раз - отдельная функция, которая принимает словарь. А если кто-то передаст «не тот» словарь?
Класс и объект
Программисты - люди ленивые.
Они придумали: давайте упакуем данные игрока и то, что он умеет, в одну коробку.
Эта коробка называется класс.
Концепция
Класс - описание того, как устроен игрок.
Сам по себе он ничего не делает.
Это шаблон, по которому потом делают настоящих игроков.
Концепция
Объект - то, что родилось из чертежа.
Один объект = один настоящий игрок со своим именем и hp.
По одному чертежу можно сделать сколько угодно объектов.
Главная мысль
Каждый со своими данными. Чертёж один.
Из чего состоит объект
Это атрибуты - данные.
Это методы - действия.
Всё ООП - про эти два слова.
Из чего состоит объект
Атрибут - переменная, «приклеенная» к объекту.
У каждого объекта свои атрибуты.
Из чего состоит объект
Метод - функция, «приклеенная» к объекту.
Все объекты одного класса умеют одно и то же.
Создание первого класса
Это весь класс. Три строки тела.
Синтаксис
Имя класса - с большой буквы. Это соглашение.
Конструктор
__init__Когда мы говорим Python «сделай игрока», он:
Player__init____init__ - от слова initialize, «настроить».
Параметр self
Когда мы пишем чертёж, мы ещё не знаем,
как зовут конкретного игрока.
self - это местоимение. Внутри класса означает «мой, этого конкретного игрока».
Параметр self
self - способ объекта обратиться к своим карманам.
Создаём первого игрока
Точка читается как «у»: «у Steve имя», «у Steve hp».
Множество объектов
Один чертёж - два разных объекта.
Множество объектов
Это две разные коробки. Физически разные.
Множество объектов
id() - уникальный адрес объекта в памяти. is сравнивает по адресу.
Десятки байт на объект. 1000 игроков ≈ 50 КБ - объекты дёшевы.
Множество объектов
b = a - не копия, а второе имя для той же коробки.
Множество объектов
Объекты не связаны друг с другом.
Изменение одного не влияет на других.
Множество объектов
Тысяча игроков. Каждый со своими данными.
То, ради чего всё и затевалось.
Методы класса
Метод - функция внутри класса. Первым параметром всегда self.
Методы класса
Вызов через точку. self Python подставит сам.
Под капотом
Когда вы пишете
Python внутри переводит это в
steve автоматически попадает на место self.
Методы класса
В скобках только amount. self Python подставляет сам.
Методы класса
Метод может возвращать значение - как обычная функция.
Методы класса
Внутри класса методы свободно общаются через self.
Магический метод
__str__: управляем print()Без __str__ вы бы увидели <__main__.Player object at 0x7f...> - адрес в памяти.
Двойные подчёркивания - соглашение для «служебных» методов, которые Python вызывает сам.
ООП в стандартных библиотеках
Каждая точка - это вызов метода у объекта.
ООП вы используете с первой лекции, просто не называли его так.
Пример
Строка - это объект класса str.
У него есть метод upper.
То же самое, что steve.say(...). Просто класс str написали разработчики Python.
Пример
df - объект класса DataFrame.
Методы: head, groupby, describe. Атрибуты: shape, columns.
Устроен точно как наш Player - только методов сотни.
Пример
LinearRegression - класс. model - объект. fit/predict - методы.
Когда вы понимаете ООП, вы понимаете, как устроены большие библиотеки.
Практика #1
name, hp (по умолчанию 20), inventory (пустой список)pickup(item) - добавить предмет в инвентарьhas(item) - есть ли предмет__str__ - <Steve> hp: 20, items: 3Практика #1
То же, но из жизни:
owner, balance (по умолчанию 0)deposit(amount), withdraw(amount)Не игра - но устроено так же.
Разбор
Перерыв
Дальше: наступает ночь, появляются мобы - и мы поймём,
почему пишем один метод вместо ста.
Проблема дублирования
Из темноты выходят мобы:
медленно идёт и бьёт в ближнем
стреляет из лука издалека
тихо подходит и взрывается
У всех общее: имя, hp, координата, могут получить урон.
Различается только то, как они атакуют.
Лобовое решение
И ещё то же самое для Creeper. Семь строк скопированы трижды.
Почему так нельзя
take_damage - поправили в Zombie, забыли в SkeletonТак делать не будем.
Наследование
Общее - в один класс-родитель.
Уникальное - в потомков.
Потомки получают общее автоматически.
Наследование
Общая часть собрана в одном месте.
Наследование
Zombie(Mob) - «Zombie наследует от Mob». Внутри - только новое умение.
Что досталось бесплатно
Мы определили только attack, но всё остальное работает.
Расширяем __init__
super().__init__(name, hp) - «сделай всё, что делает родитель, а потом я добавлю своё».
Внимание
Переопределили __init__ - первой строкой super().__init__(...).
Слова
parent, базовый, суперкласс
child, наследники, подклассы
Запись Zombie(Mob) читается: «Zombie - это Mob плюс ещё кое-что».
Переопределение и полиморфизм
Одинаковое имя метода - разная реализация.
Какую версию вызвать?
Python смотрит на тип объекта и зовёт нужную версию attack.
Магия в одном цикле
Циклу неважно, кто внутри. Он просто зовёт .attack().
Это и называется полиморфизм. Идея простая: один интерфейс, разное поведение.
ООП в реальных проектах
Разные модели - один интерфейс fit/predict.
Тот же цикл, что был с мобами.
Тоже наследование
ValueError - разновидность Exception.
Когда вы пишете except Exception - ловятся все, потому что наследование.
Частые ошибки
Атрибут создаётся только через self.имя = ...
Частые ошибки
Переопределили __init__ - первой строкой super().__init__(...).
Частые ошибки
Списки и словари - только в __init__, а не на уровне класса.
Практика #2
Без Майнкрафта - закрепляем приём на простом примере.
Animal(name) с методом sound() = "..."Dog(Animal) переопределяет sound() = "Гав"Cat(Animal) переопределяет sound() = "Мяу"describe() в Animal: «{name} говорит {sound()}»Разбор
Тот же приём, что с Zombie/Skeleton/Creeper. Тот же цикл. Просто другие имена.
Итоги
self - «мой» внутри класса__str__ - управляем тем, как объект печатаетсяsuper() - позвать родителяИтоги
Когда вы видите df.groupby(...) или model.fit(...), теперь вы знаете, как это устроено внутри.
Завершение
Telegram: @gokalqurt