2.22. Спадкування¶
Спадкування дозволяє класу розширювати інший клас – починати з усіма його методами та атрибутами, а потім додавати або перевизначати те, що відрізняється. Оригінальний клас називається базовим (або батьківським); розширення називається підкласом.
Square повторно використовує кожен метод Shape і перевизначає ті, які потрібно спеціалізувати.¶
2.22.1. Визначення підкласу¶
Базовий клас вказується в дужках на рядку class:
class Shape:
def __init__(self, name):
self.name = name
def describe(self):
return self.name + " with area " + str(self.area())
def area(self):
return 0
class Square(Shape):
def __init__(self, side):
super().__init__("square")
self.side = side
def area(self):
return self.side * self.side
s = Square(4)
print(s.describe())
Виведення:
square with area 16
Square успадковує describe від Shape без змін і перевизначає area, щоб повернути реальне значення. Метод describe базового класу все одно працює, оскільки він викликає self.area() – яке вирішується на перевизначення під час виконання.
2.22.2. super()¶
super() повертає проксі, який дозволяє методу викликати версію методу базового класу. Найчастіше використовується для виклику базового __init__ з __init__ підкласу, щоб базовий клас міг ініціалізувати власні атрибути:
class Square(Shape):
def __init__(self, side):
super().__init__("square") # run Shape.__init__
self.side = side
Забутий super().__init__ є поширеним джерелом помилок: атрибути, які мав би встановити базовий клас, ніколи не встановлюються, і успадковані методи, що покладаються на них, згодом аварійно завершуються.
2.22.2.1. Неявний базовий клас¶
Кожен клас неявно успадковує від object, кореня ієрархії типів Python. Методи, такі як __repr__, __eq__, та механізм доступу до атрибутів – усе це надходить звідти, навіть якщо клас не оголошує базового. Явне написання class Foo(object): і написання class Foo: є еквівалентними в сучасному Python; останнє є прийнятою формою.
2.22.3. Коли спадкування допомагає – і коли ні¶
Використовуйте спадкування, коли один клас є справді більш конкретним видом іншого, поділяючи більшу частину поведінки. Класичний тест – перевірка «є-різновидом»: Square є Shape.
Коли два класи просто мають кілька спільних допоміжних функцій, спадкування є зайвим. Натомість зверніться до композиції: нехай один клас тримає екземпляр іншого як атрибут і використовує його через цей атрибут. Принцип – «має», а не «є»:
class Logger:
def log(self, msg):
print("[log]", msg)
# inheritance: Worker IS-A Logger
class WorkerInherits(Logger):
def run(self):
self.log("starting")
# composition: Worker HAS-A Logger
class WorkerComposes:
def __init__(self):
self.logger = Logger()
def run(self):
self.logger.log("starting")
Обидва варіанти працюють. Версія з композицією зазвичай краща, тому що вона:
Зменшує інтерфейси.
WorkerComposesнадає лишеrunіlogger.WorkerInheritsтакож надаєlog– той, хто викликає, може писатиworker.log(...)безпосередньо, незалежно від того, чи це було задумано.Розв’язує терміни існування. Logger можна замінити, поділити між workers або побудувати по-різному для кожного worker, не торкаючись класу
Worker. Підклас нерозривно прив’язаний до свого базового.Уникає зіткнень імен методів. Два базові класи, що обидва визначають
run, конфліктують, коли клас намагається успадкувати від обох; два атрибути ніколи не конфліктують.
Практичне правило:
Використовуйте спадкування, коли підклас справді є різновидом базового і кожен метод базового має сенс у ньому –
SquareєShape;MemoryErrorєException.Використовуйте композицію для всього іншого – коли вам потрібна лише поведінка іншого класу, а не його ідентичність. У більшості реального коду композиція використовується значно частіше, ніж спадкування.