В прошлом уроке подкласс либо брал метод базового класса как есть, либо заменял его целиком. Чаще нужно
третье: дополнить метод базового класса — сделать всё то же самое и ещё немного. Для этого есть super().
Свой __init__ в подклассе
У выпечки, кроме названия и цены, есть вес. Значит, подклассу Pastry нужен свой __init__ с параметром
weight. Но переписывать в нём self.name = name и self.price = price не стоит: это уже умеет __init__
базового класса. Вызов super().__init__(…) выполняет __init__ базового класса для этого же экземпляра:
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} ₽"
def cost(self, quantity):
return self.price * quantity
class Pastry(Product):
def __init__(self, name, price, weight):
super().__init__(name, price) # name и price записывает Product
self.weight = weight
croissant = Pastry("Круассан", 140, 70)
print(croissant.name, croissant.price, croissant.weight)
print(croissant.label())Круассан 140 70 Круассан — 140 ₽
super() — это «тот же self, но методы ищутся начиная с класса, следующего за тем, где написан
super()»; пока у класса один базовый класс, это и есть базовый класс. Поэтому self в скобки не
передают: super().__init__(name, price).
Объявите подкласс Drink класса Product: Drink("Латте", 220, 300) — название, цена и объём volume
в миллилитрах. Название и цену пусть записывает __init__ базового класса. Добавьте метод per_100ml() —
цена за 100 мл, округлённая до одного знака.
__init__ подкласса — super().__init__(name, price), затем self.volume = volume.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Drink(Product):
def __init__(self, name, price, volume):
...
def per_100ml(self):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Drink(Product):
def __init__(self, name, price, volume):
super().__init__(name, price)
self.volume = volume
def per_100ml(self):
return round(self.price / self.volume * 100, 1)class Cake(Product):
def __init__(self, name, price, slices):
self.slices = slices # забыли super().__init__
cake = Cake("Медовик", 1200, 8)
print(cake.slices)
print(cake.label())8
AttributeError: 'Cake' object has no attribute 'name'
Расширить, а не скопировать
Так же super() работает в любом методе: подкласс вызывает версию базового класса и что-то добавляет к её
результату. Если потом формат строки в Product.label поменяется, подкласс подхватит изменение сам:
class Pastry(Product):
def __init__(self, name, price, weight):
super().__init__(name, price)
self.weight = weight
def label(self):
return f"{super().label()}, {self.weight} г"
print(Pastry("Круассан", 140, 70).label())Круассан — 140 ₽, 70 г
Объявите подкласс SeasonalProduct класса Product, в котором label() возвращает строку базового класса
и пометку: "Глинтвейн — 300 ₽ (до 31 декабря)". Дату окончания сезона передают при создании:
SeasonalProduct("Глинтвейн", 300, "31 декабря"), она хранится в атрибуте until.
__init__ базового класса; подклассу остаётся until.label — результат super().label() плюс пометка в скобках.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class SeasonalProduct(Product):
def __init__(self, name, price, until):
...
def label(self):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class SeasonalProduct(Product):
def __init__(self, name, price, until):
super().__init__(name, price)
self.until = until
def label(self):
return f"{super().label()} (до {self.until})"У Product есть метод cost(quantity) — стоимость quantity штук. Объявите подкласс WholesaleProduct:
его cost от 10 штук даёт скидку 15 % (округление до целых), а меньше 10 штук считает так же, как
Product. Используйте super().cost(quantity), а не повторяйте умножение.
full = super().cost(quantity); от 10 штук — round(full * 85 / 100).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class WholesaleProduct(Product):
def cost(self, quantity):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class WholesaleProduct(Product):
def cost(self, quantity):
full = super().cost(quantity)
if quantity >= 10:
return round(full * 85 / 100)
return fullПорядок поиска методов: MRO
Куда именно смотрит super()? У каждого класса есть порядок разрешения методов (method resolution order,
MRO) — список классов, в котором Python ищет атрибуты: сам класс, его базовый класс, базовый класс базового и
так далее. В конце всегда стоит object — базовый класс всех классов. MRO хранится в атрибуте __mro__:
print([cls.__name__ for cls in Pastry.__mro__])
print([cls.__name__ for cls in WholesaleProduct.__mro__])['Pastry', 'Product', 'object'] ['WholesaleProduct', 'Product', 'object']
super() берёт следующий класс в MRO после текущего. Пока у класса один базовый класс, MRO — просто цепочка
вверх. У класса может быть и несколько базовых — множественное наследование (multiple inheritance), —
тогда MRO решает, чей метод найдётся первым. В этом курсе оно не понадобится; для начала хватает правила:
один базовый класс, остальное — композицией (модуль 6).
Каким классом заканчивается Product.__mro__ у класса, объявленного как class Product: — без базового класса?
object — от него приходят __repr__ с адресом, __eq__ по тождественности и другие методы по умолчанию. type — класс самих классов, а не их базовый класс.