Inherit - Наследование
Наследование позволяет создавать новый класс на основе уже существующего класса. Наряду с инкапсуляцией наследование является одним из краеугольных камней объектно-ориентированного дизайна.
Ключевыми понятиями наследования являются подкласс и суперкласс. Подкласс наследует от суперкласса все публичные атрибуты и методы. Суперкласс еще называется базовым (base class) или родительским (parent class), а подкласс - производным (derived class) или дочерним (child class).
Синтаксис для наследования классов выглядит следующим образом:
class подкласс (суперкласс):
методы_подкласса
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()
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
2
3
4
5
6
7
8
9
10
11
12
13
В данном случае классы KitchenTable и DeskTable не имеют своих собственных конструкторов, поэтому наследуют его от родительского класса. При создании экземпляров этих столов, передавать аргументы для init() обязательно, иначе возникнет ошибка:
t1 = KitchenTable()
В консоль выводиться ошибка: «отсутствует 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'
2
3
Верный код:
t1 = KitchenTable(2, 2, 0.7)
t2 = DeskTable(1.5, 0.8, 0.75)
t3 = KitchenTable(1, 1.2, 0.8)
2
3
Несомненно можно создавать столы и от родительского класса Table. Однако он не будет, согласно неким родственным связям, иметь доступ к методам setPlaces() и square(). Точно также как объект класса KitchenTable не имеет доступа к единоличным атрибутам сестринского класса DeskTable:
t4 = Table(1, 1, 0.5)
print(t2.square()) # 1.2000000000000002
print(t4.square())
2
3
В консоль выводиться ошибка:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Table' object has no attribute 'square'
2
3
Код:
print(t3.square())
В консоль выводиться ошибка:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'KitchenTable' object has no attribute 'square'
2
3
В этом смысле терминология «родительский и дочерний класс» не совсем верна. Наследование в ООП – это скорее аналог систематизации и классификации наподобие той, что есть в живой природе. Все млекопитающие имеют четырехкамерное сердце, но только носороги – рог.
Полное переопределение метода надкласса
Что если в подклассе нам не подходит код метода его надкласса. Допустим, мы вводим еще один класс столов, который является дочерним по отношению к DeskTable. Пусть это будут компьютерные столы, при вычислении рабочей поверхности которых надо отнимать заданную величину. Имеет смысл внести в этот новый подкласс его собственный метод square():
class ComputerTable(DeskTable):
def square(self, e):
return self.width * self.length - e
2
3
При создании объекта типа ComputerTable по-прежнему требуется указывать параметры, так как интерпретатор в поисках конструктора пойдет по дереву наследования сначала в родителя, а потом в прародителя и найдет там метод __init__()
.
Однако когда будет вызываться метод square(), то поскольку он будет обнаружен в самом ComputerTable, то метод square() из DeskTable останется невидимым, т. е. для объектов класса ComputerTable он окажется переопределенным:
ct = ComputerTable(2, 1, 1)
print(ct.square(0.3)) # 1.7
2
Дополнение, оно же расширение, метода
Если посмотреть на вычисление площади, то часть кода надкласса дублируется в подклассе. Этого можно избежать, если вызвать родительский метод, а потом дополнить его:
class ComputerTable(DeskTable):
def square(self, e):
return DeskTable.square(self) - e
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
2
3
4
5
6
Однако это не лучший способ, если дублируется почти весь конструктор надкласса. Проще вызвать родительский конструктор, после чего дополнить своим кодом:
class KitchenTable(Table):
def __init__(self, l, w, h, p):
Table.__init__(self, l, w, h)
self.places = p
2
3
4
Теперь при создании объекта типа KitchenTable надо указывать в конструкторе четыре аргумента. Три из них будут переданы выше по лестнице наследования, а четвертый оприходован на месте:
tk = KitchenTable(2, 1.5, 0.7, 10)
print(tk.places) # 10
print(tk.width) # 1.5
2
3
Упражнения
Написать базовый класс «табурет», от которого сделать наследники стул и кресло, с добавлением соответствующих полей. И методов выводящих все свойства созданного объекта для каждого отдельного класса.
Генерация интерфейса: 5 меток с произвольной надписью.
Генерация интерфейса: 5 меток с номер элемента (начиная с первого).
Генерация интерфейса: 5 меток с номер элемента (начиная с первого) произвольной надписью.
Генерация интерфейса: 5 меток с номер элемента (начиная с первого) надписью будут дни недели:
week_days = [«Monday», «Tuesday», «Wednesday», «Thursday», «Friday»]
1Генерация интерфейса: 5 однострочное текстовых полей
Генерация интерфейса: по 5 элементов метка и текстовое поле
Используя оператор if создать 6 элементов: нечетные - метка, четные - текстовое поле
Создать 5 элементов: метка + текстовое поле, но четвертый элемент - кнопка
Написать интерфейс программы для табуреток содержащий:
- метки и текстовые поля для вода данных;
- кнопку при нажатии на которую все данные с подписями выводятся в нижнее текстовое поле.
Пример интерфейса: