Перейти до змісту

Доступ до атрибутів базового класа

Уявімо ситуацію, що в базовому класі, від якого ми будемо успадковувати наш новий клас, вже реалізовано певний метод, котрий підходить нам по своїй функціональності, але у ньому не вистачає певних речей, або ж нам треба дещо змінити його функціонал. Звісно, що ми можемо повністю переписати цей метод у нашому новому класі, але з великою ймовірністю ми стикнемось з повторним використання коду. І якщо, припустимо, ми вносимо зміни в метод базового класа, то такіж зміни нам доведеться вносити і в аналогічний метод нашого нового класа, що є небажаним (підвищується ймовірність припуститись помилки, зайва робота в решті решт).

Якщо у дочірньому класі певний атрибут було перевизначено, а потрібен доступ до відповідного атрибута базового класа, в Python це можна зробити двома способами.

Один з них полягає у тому, що ми явно вказуємо базовий клас, відповідний атрибут і, при необхідності, передаємо екземпляр дочірнього класа (параметр self).

Розглянемо приклад.

>>> class Person:
...     def __init__(self, name):
...         self.name = name.title()
...     def say_hello(self):
...         print('Hi, I am', self.name)
...
>>> p = Person('john')
>>> p.say_hello()
Hi, I am John
>>>

Зауважимо, що в конструкторі класа відбувається певна маніпуляція зі вхідними даними.

Тепер нам треба створити клас, який описував би не просто людину, а співробітника певної організації. Співробітник має усі атрибути, які має і людина, зокрема ім'я. Власне будь-який співробітник і є людиною, тому логічно успадкуватись від класа Person. Крім того співробітник має ще й заробітню плату.

>>> class Employee(Person):
...     def __init__(self, name, salary):
...         Person.__init__(self, name)
...         self.salary = salary
...     def say_hello(self):
...         Person.say_hello(self)
...         print('My salary is', self.salary)
...
>>> e = Employee('JANE', 120)
>>> e.say_hello()
Hi, I am Jane
My salary is 120
>>>

В конструкторі дочірнього класа ми викликаємо конструктор базового класа, при цьому передаючи йому екземпляр дочірнього класа і необхідні дані для ініціалізації атрибутів. В конструкторі базового класа відбувається певна маніпуляція над вхідними даними і ініціалізація атрибута name. І вже потім в конструкторі дочірнього класа відбувається ініціалізація атрибута salary.

Аналогічно з метода say_hello() дочірнього класа викликається відповідний метод базового класа.

Недоліки такого підхода:

  • ускладнюється підтримка кода якщо нам треба щось поміняти в ієрархії класів
  • логіка кода чітко прив'язана до ієрархії успадкування класів і схильна до помилок, особливо при використанні множинного успадкування.

super()

Існує ще один спосіб доступу до атрибутів базового класа, який позбавлений недоліків попереднього.

В Python є спеціальний вбудований клас super, екземпляри якого є спеціальними проксі-об'єктами (об'єктами-посередниками).

super(type)

Такі об'єкти надають доступ до атрибутів наступного класа у ланцюжку лінеаризації класа type.

Якщо з базовим класом треба зв'язати і екземпляр, його передають другим аргументом при інстанціюванні super:

super(type, obj)

Але якщо екземпляр super створюється всередині метода класа, то можна не вказувати ні клас, ні екземпляр класа. Python сам "знайде" необхідне. Таким чином, за допомогою super можна отримати доступ до атрибутів суперкласа, не вказуючи його імені, причому це буде давати коректні результати навіть при використанні множинного успадкування.

Перепишемо попередній приклад використовуючи клас super:

>>> class Person:
...     def __init__(self, name):
...             self.name = name.title()
...     def say_hello(self):
...             print('Hi, I am', self.name)
...
>>>
... class Employee(Person):
...     def __init__(self, name, salary):
...             super().__init__(name)
...             self.salary = salary
...     def say_hello(self):
...             super().say_hello()
...             print('My salary is', self.salary)
...
>>>
>>> e = Employee('janE', 120)
>>> e.say_hello()
Hi, I am Jane
My salary is 120
>>>

З класа Employee ми отримуємо доступ до атрибутів класа Person за допомогою super(). Зауважте: нам тепер не треба вказувати навіть поточний екземпляр класа.

Тепер звернемось до множинної спадковості:

>>> class Animal:
...     def __init__(self):
...             self.can_run = False
...             self.can_fly = False
...
>>>
>>> class Horse(Animal):
...     def __init__(self):
...             super().__init__()
...             self.can_run = True
...
>>>
>>> class Eagle(Animal):
...     def __init__(self):
...             super().__init__()
...             self.can_fly = True
...
>>>
>>> class Pegasus(Horse, Eagle):
...     pass
...
>>> p = Pegasus()
>>> p.can_run
True
>>> p.can_fly
True
>>>

Щоб краще зрозуміти, як це працює, давайте спочатку вивчимо лінеаризацію класа Pegasus:

>>> Pegasus.mro()
[<class '__main__.Pegasus'>, <class '__main__.Horse'>, <class '__main__.Eagle'>, <class '__main__.Animal'>, <class 'object'>]
>>>

Тепер покроково:

  1. В класі Pegasus конструктора не оголошено, отже згідно лінеаризації викликаємо конструктор класа Horse
  2. В конструкторі Horse за допомогою super() викликається конструктор "попереднього" класа, згідно MRO це буде конструктор класа Eagle
  3. В конструкторі Eagle за допомогою super() викликається конструктор "попереднього" класа, згідно MRO це буде конструктор класа Animal
Back to top