Inherit - Наследование

Наследование позволяет создавать новый класс на основе уже существующего класса. Наряду с инкапсуляцией наследование является одним из краеугольных камней объектно-ориентированного дизайна.

Ключевыми понятиями наследования являются подкласс и суперкласс. Подкласс наследует от суперкласса все публичные атрибуты и методы. Суперкласс еще называется базовым (base class) или родительским (parent class), а подкласс - производным (derived class) или дочерним (child class).

Синтаксис для наследования классов выглядит следующим образом:

class подкласс (суперкласс):
    методы_подкласса
1
2

Например, класс Person, который представляет человека. Предположим, нам необходим класс работника, который работает на некотором предприятии. Мы могли бы создать с нуля новый класс, к примеру, класс Employee. Однако он может иметь те же атрибуты и методы, что и класс Person, так как сотрудник - это человек. Поэтому нет смысла определять в классе Employee тот же функционал, что и в классе Person. И в этом случае лучше применить наследование.

Итак, унаследуем класс Employee от класса Person:

class Person:
    def __init__(self, name, age):
        self.name = name  # устанавливаем имя
        self.age = age  # устанавливаем возраст

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age in range(1, 100):
            self.__age = age
        else:
            print("Недопустимый возраст")

    @property
    def name(self):
        return self.__name

    def display_info(self):
        print("Имя:", self.__name, "\tВозраст:", self.__age)

class Employee(Person):
    def details(self, company):
        # print(self.__name, "работает в компании", company) # так нельзя, self.__name - приватный атрибут
        print(self.name, "работает в компании", company)

tom = Employee("Tom", 23)
tom.details("Google")
tom.age = 33
tom.display_info()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

Класс Employee полностью перенимает функционал класса Person и в дополнении к нему добавляет метод details().

Стоит обратить внимание, что для Employee доступны через ключевое слово self все методы и атрибуты класса Person, кроме закрытых атрибутов типа __name или __age.

При создании объекта Employee мы фактически используем конструктор класса Person. И кроме того, у этого объекта мы можем вызвать все методы класса Person.

Простое наследование методов родительского класса

В качестве примера рассмотрим разработку класса столов и его двух подклассов – кухонных и письменных столов. Все столы, независимо от своего типа, имеют длину, ширину и высоту. Пусть для письменных столов важна площадь поверхности, а для кухонных – количество посадочных мест. Общее вынесем в класс, частное – в подклассы.

Связь между родительским и дочерним классом устанавливается через дочерний: родительские классы перечисляются в скобках после его имени:

class Table:
    def __init__(self, l, w, h):
        self.lenght = l
        self.width = w
        self.height = h

class KitchenTable(Table):
    def setPlaces(self, p):
        self.places = p

class DeskTable(Table):
    def square(self):
        return self.width * self.length
1
2
3
4
5
6
7
8
9
10
11
12
13

В данном случае классы KitchenTable и DeskTable не имеют своих собственных конструкторов, поэтому наследуют его от родительского класса. При создании экземпляров этих столов, передавать аргументы для init() обязательно, иначе возникнет ошибка:

t1 = KitchenTable()
1

В консоль выводиться ошибка: «отсутствует 3 обязательных позиционных аргумента: „l“, „w“ и „h“»:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() missing 3 required positional arguments: 'l', 'w', and 'h'
1
2
3

Верный код:

t1 = KitchenTable(2, 2, 0.7)
t2 = DeskTable(1.5, 0.8, 0.75)
t3 = KitchenTable(1, 1.2, 0.8)
1
2
3

Несомненно можно создавать столы и от родительского класса Table. Однако он не будет, согласно неким родственным связям, иметь доступ к методам setPlaces() и square(). Точно также как объект класса KitchenTable не имеет доступа к единоличным атрибутам сестринского класса DeskTable:

t4 = Table(1, 1, 0.5)
print(t2.square())                 # 1.2000000000000002
print(t4.square())
1
2
3

В консоль выводиться ошибка:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Table' object has no attribute 'square'
1
2
3

Код:

print(t3.square())
1

В консоль выводиться ошибка:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'KitchenTable' object has no attribute 'square'
1
2
3

В этом смысле терминология «родительский и дочерний класс» не совсем верна. Наследование в ООП – это скорее аналог систематизации и классификации наподобие той, что есть в живой природе. Все млекопитающие имеют четырехкамерное сердце, но только носороги – рог.

Полное переопределение метода надкласса

Что если в подклассе нам не подходит код метода его надкласса. Допустим, мы вводим еще один класс столов, который является дочерним по отношению к DeskTable. Пусть это будут компьютерные столы, при вычислении рабочей поверхности которых надо отнимать заданную величину. Имеет смысл внести в этот новый подкласс его собственный метод square():

class ComputerTable(DeskTable):
    def square(self, e):
        return self.width * self.length - e
1
2
3

При создании объекта типа ComputerTable по-прежнему требуется указывать параметры, так как интерпретатор в поисках конструктора пойдет по дереву наследования сначала в родителя, а потом в прародителя и найдет там метод __init__().

Однако когда будет вызываться метод square(), то поскольку он будет обнаружен в самом ComputerTable, то метод square() из DeskTable останется невидимым, т. е. для объектов класса ComputerTable он окажется переопределенным:

ct = ComputerTable(2, 1, 1)
print(ct.square(0.3))                  # 1.7
1
2

Дополнение, оно же расширение, метода

Если посмотреть на вычисление площади, то часть кода надкласса дублируется в подклассе. Этого можно избежать, если вызвать родительский метод, а потом дополнить его:

class ComputerTable(DeskTable):
    def square(self, e):
        return DeskTable.square(self) - e
1
2
3

Здесь вызывается метод другого класса, а потом дополняется своими выражениями. В данном случае вычитанием.

Рассмотрим другой пример. Допустим, в классе KitchenTable нам не нужен метод, поле places должно устанавливаться при создании объекта в конструкторе. В классе можно создать собсвенный конструктор с чистого листа, чем переопределить родительский:

class KitchenTable(Table):
    def __init__(self, l, w, h, p):
        self.length = l
        self.width = w
        self.height = h
        self.places = p
1
2
3
4
5
6

Однако это не лучший способ, если дублируется почти весь конструктор надкласса. Проще вызвать родительский конструктор, после чего дополнить своим кодом:

class KitchenTable(Table):
    def __init__(self, l, w, h, p):
        Table.__init__(self, l, w, h)
        self.places = p
1
2
3
4

Теперь при создании объекта типа KitchenTable надо указывать в конструкторе четыре аргумента. Три из них будут переданы выше по лестнице наследования, а четвертый оприходован на месте:

tk = KitchenTable(2, 1.5, 0.7, 10)
print(tk.places)                    # 10
print(tk.width)                     # 1.5
1
2
3

Упражнения

  1. Написать базовый класс «табурет», от которого сделать наследники стул и кресло, с добавлением соответствующих полей. И методов выводящих все свойства созданного объекта для каждого отдельного класса.

  2. Генерация интерфейса: 5 меток с произвольной надписью.

  3. Генерация интерфейса: 5 меток с номер элемента (начиная с первого).

  4. Генерация интерфейса: 5 меток с номер элемента (начиная с первого) произвольной надписью.

  5. Генерация интерфейса: 5 меток с номер элемента (начиная с первого) надписью будут дни недели:

    week_days = [«Monday», «Tuesday», «Wednesday», «Thursday», «Friday»]
    
    1
  6. Генерация интерфейса: 5 однострочное текстовых полей

  7. Генерация интерфейса: по 5 элементов метка и текстовое поле

  8. Используя оператор if создать 6 элементов: нечетные - метка, четные - текстовое поле

  9. Создать 5 элементов: метка + текстовое поле, но четвертый элемент - кнопка

  10. Написать интерфейс программы для табуреток содержащий:

    • метки и текстовые поля для вода данных;
    • кнопку при нажатии на которую все данные с подписями выводятся в нижнее текстовое поле.

    Пример интерфейса:

    app