В меню «Кофейной лавки» есть обычные товары, сезонные напитки и выпечка. Всё это товары: у всех название,
цена, строка для меню. Но у сезонных напитков своя пометка, а выпечку предлагают в комплекте с кофе.
Копировать класс Product три раза и менять по строчке — плохая идея: исправление пришлось бы вносить
в три места. Вместо этого классы строят на основе другого класса.
class B(A)
Если в скобках после имени класса указать другой класс, новый класс наследует (inheritance) всё, что есть
у того: атрибуты класса и методы, включая __init__. Класс в скобках называют базовым (base class,
«родитель»), а новый — подклассом (subclass, «дочерний класс»):
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __repr__(self):
return f"{type(self).__name__}({self.name!r}, {self.price})"
def label(self):
return f"{self.name} — {self.price} ₽"
class Snack(Product):
pass
cookie = Snack("Овсяное печенье", 90)
print(cookie.label())
print(cookie)Овсяное печенье — 90 ₽
Snack('Овсяное печенье', 90)У Snack нет своего тела, кроме pass, но экземпляр создаётся с названием и ценой и умеет label() —
всё это пришло из Product. Поиск атрибута продолжается вверх по иерархии: сначала экземпляр, потом его
класс, потом базовый класс.
Свои методы и переопределение
Подкласс может добавить методы, которых нет у базового класса, и переопределить (override) существующие — объявить метод с тем же именем. Тогда для экземпляров подкласса работает новая версия, а для экземпляров базового класса — старая:
class Tea(Product):
def brew_minutes(self): # новый метод
return 4
def label(self): # своя версия метода базового класса
return f"{self.name} — {self.price} ₽, заваривать {self.brew_minutes()} мин"
print(Tea("Улун", 180).label())
print(Product("Улун", 180).label())Улун — 180 ₽, заваривать 4 мин Улун — 180 ₽
Объявите подкласс SeasonalProduct класса Product из демонстрации. Переопределите в нём label():
к обычной строке добавляется пометка — "Тыквенный латте — 290 ₽ (сезонное)".
class SeasonalProduct(Product): и метод label(self), который собирает строку из self.name и self.price.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class SeasonalProduct: # укажите базовый класс
def label(self):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class SeasonalProduct(Product):
def label(self):
return f"{self.name} — {self.price} ₽ (сезонное)"Объявите подкласс Pastry класса Product с новым методом combo_price(drink): цена комплекта «выпечка +
напиток» со скидкой 10 %, округлённая до целых рублей. drink — любой товар с атрибутом price.
self.price + drink.price; со скидкой 10 % — умножить на 90 и разделить на 100, затем round.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Pastry(Product):
def combo_price(self, drink):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Pastry(Product):
def combo_price(self, drink):
return round((self.price + drink.price) * 90 / 100)isinstance и issubclass в иерархии
Экземпляр подкласса — это и экземпляр базового класса: круассан — тоже товар. Поэтому isinstance для
него возвращает True с обоими классами. А issubclass(A, B) проверяет классы: построен ли A на основе B:
croissant = Pastry("Круассан", 140)
print(isinstance(croissant, Pastry), isinstance(croissant, Product))
print(isinstance(Product("Чай", 120), Pastry))
print(issubclass(Pastry, Product), issubclass(SeasonalProduct, Pastry))
print(issubclass(bool, int))True True False True False True
Вспомните вопрос из урока «Всё в Python — объект»: isinstance(True, int) — True, потому что bool — подкласс int.
def shelf_of(item):
if isinstance(item, Product): # ошибка: базовый класс первым
return "общая полка"
elif isinstance(item, Pastry):
return "витрина с выпечкой"
return "?"
for item in [Product("Чай", 120), Pastry("Круассан", 140)]:
print(item.name, "→", shelf_of(item))Чай → общая полка Круассан → общая полка
Напишите функцию kind_of(item): она возвращает "сезонное" для SeasonalProduct, "выпечка" для Pastry
и "товар" для любого другого Product.
isinstance(item, Product) — или просто return "товар" — в конце.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
def kind_of(item):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
def kind_of(item):
if isinstance(item, SeasonalProduct):
return "сезонное"
if isinstance(item, Pastry):
return "выпечка"
return "товар"Что вернёт issubclass(Product, Pastry)?
issubclass смотрит в одну сторону: Pastry построен на основе Product, а не наоборот. Выпечка — товар, но не всякий товар — выпечка.Наследование хорошо, когда подкласс является разновидностью базового класса: сезонный товар — товар, выпечка — товар. В следующем уроке подкласс научится не заменять метод базового класса целиком, а расширять его.