Нашли ошибку?
Скачать чистый
урок 2 из 4≈ 20 минупражнения: 0 из 3

super()

super() вызывает метод базового класса из подкласса: свой __init__ с дополнительными атрибутами, расширение метода вместо копирования, порядок поиска методов MRO и забытый super().__init__.

Python запустится при первом выполнении

В прошлом уроке подкласс либо брал метод базового класса как есть, либо заменял его целиком. Чаще нужно третье: дополнить метод базового класса — сделать всё то же самое и ещё немного. Для этого есть super().

Свой __init__ в подклассе

У выпечки, кроме названия и цены, есть вес. Значит, подклассу Pastry нужен свой __init__ с параметром weight. Но переписывать в нём self.name = name и self.price = price не стоит: это уже умеет __init__ базового класса. Вызов super().__init__(…) выполняет __init__ базового класса для этого же экземпляра:

[ ]Pastry с весом
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).

[ ]Упражнение 1Напиток с объёмом решено

Объявите подкласс Drink класса Product: Drink("Латте", 220, 300) — название, цена и объём volume в миллилитрах. Название и цену пусть записывает __init__ базового класса. Добавьте метод per_100ml() — цена за 100 мл, округлённая до одного знака.

class Drink(Product):
    def __init__(self, name, price, volume):
        ...

    def per_100ml(self):
        ...

[ ]super().__init__ не вызвалиошибка: AttributeError
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 поменяется, подкласс подхватит изменение сам:

[ ]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 г
[ ]Упражнение 2Пометка без копирования решено

Объявите подкласс SeasonalProduct класса Product, в котором label() возвращает строку базового класса и пометку: "Глинтвейн — 300 ₽ (до 31 декабря)". Дату окончания сезона передают при создании: SeasonalProduct("Глинтвейн", 300, "31 декабря"), она хранится в атрибуте until.

class SeasonalProduct(Product):
    def __init__(self, name, price, until):
        ...

    def label(self):
        ...

[ ]Упражнение 3Оптовая цена решено

У Product есть метод cost(quantity) — стоимость quantity штук. Объявите подкласс WholesaleProduct: его cost от 10 штук даёт скидку 15 % (округление до целых), а меньше 10 штук считает так же, как Product. Используйте super().cost(quantity), а не повторяйте умножение.

class WholesaleProduct(Product):
    def cost(self, quantity):
        ...

Порядок поиска методов: MRO

Куда именно смотрит super()? У каждого класса есть порядок разрешения методов (method resolution order, MRO) — список классов, в котором Python ищет атрибуты: сам класс, его базовый класс, базовый класс базового и так далее. В конце всегда стоит object — базовый класс всех классов. MRO хранится в атрибуте __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: — без базового класса?

Нашли ошибку?