Объяснение функции super() и MRO

О функции super() часто говорят в отношении родительского класса дочернего класса. Однако функция super() не обязательно означает, что дочерний класс будет вызывать родительский класс — вместо этого super() вызывает следующий класс в порядке разрешения методов . Но прежде чем мы углубимся в это, давайте сначала рассмотрим некоторые основы.

super() и родительский класс

Если вы работали с классами в Python раньше, вы, вероятно, знакомы с функцией super(), которую можно использовать для вызова метода родительского класса (пара примеров ниже).

Например, если у нас есть родительский класс Robot с методом beep(), а затем создается класс Android, мы можем захотеть переопределить функцию beep() из нашего родительского класса.

Мы делаем это, просто создавая метод с тем же именем, что и метод родительского класса, который мы хотим переопределить:

# Parent class. 
class Robot:
  def __init__(self, model):
    self.model = model

  def vocalise(self):
      print("Beep!")

# Child class.
class Android(Robot):
  def vocalise(self):
    print("I am an android.")

spam = Robot("R2-D2")
spam.vocalise()
"Beep!"

ham = Android("Bishop")
ham.vocalise()
"I am an android."

Но, возможно, вы хотите вызвать исходный метод родительского класса в переопределенном методе. Вы можете сделать это с помощью функции super():

# Parent class. 
class Robot:
  def __init__(self, model):
    self.model = model

  def vocalise(self):
      print("Beep!")

# Child class.
class Android(Robot):
  def vocalise(self):
    super().vocalise()
    print("I am an android.")

eggs = Android("Bishop")
eggs.vocalise()
"Beep"
"I am an android."

Вы также используете функцию super(), когда хотите наследовать атрибуты от родительского класса, а также определить атрибуты, характерные для дочернего класса:

# Parent class. 
class Robot:
  def __init__(self, model):
    self.model = model

  def vocalise(self):
      print("Beep!")

# Child class.
class Android(Robot):
    def __init__(self, model, age):
       super().__init__(model)
       self.age = age

    def vocalise(self):
        super().beep()
        print("I am an android.")

foo = Android("Bishop", 25)
foo.age
25

Порядок разрешения метода

Создадим новый класс — HomeAndroid :

# Parent class. 
class Robot:
  def __init__(self, model):
    self.model = model

  def vocalise(self):
      print("Beep!")

# Child class of Robot.
class Android(Robot):
    def vocalise(self):
        print("I am an android.")

# Child class of Android.
class HomeAndroid(Android):
      def vocalise(self):
          super().vocalise()
          print("I will clean your house for you!")

Теперь вы можете ожидать, что функция super() вызовет класс Robot — в конце концов, класс Android является дочерним классом класса Robot.

Однако это не так:

bar = HomeAndroid("Klara")
bar.vocalise()
"I am an android."
"I will clean your house for you!"

Python просматривает порядок разрешения методов, чтобы найти класс, из которого следует вызвать функцию vocalise() (если он существует).

Вы можете просмотреть MRO, вызвав метод mro() для объекта класса:

HomeAndroid.mro()
[<class '__main__.HomeAndroid'>, <class '__main__.Android'>, <class '__main__.Robot'>, <class 'object'>]

Как видите, Python проверяет унаследованные классы слева направо (при этом дочерние классы появляются перед родительским классом). Поскольку он обнаружил метод vocalise() класса Android перед методом vocalise() класса Robot, он вызывает этот метод.

Вызов mro() для объекта класса возвращает список (как указано выше), но вызов __mro__ attribute объекта класса возвращает кортеж.

Так что же произойдет, если в классе Android не будет метода vocalise()? Тогда Python, проверив MRO и не найдя метод vocalise() в классе Android, вызовет метод vocalise() класса Robot:

# Parent class. 
class Robot:
  def __init__(self, model):
    self.model = model

  def vocalise(self):
      print("Beep!")

# Child class of Robot.
class Android(Robot):
      pass

# Child class of Android.
class HomeAndroid(Android):
      def vocalise(self):
          super().vocalise()
          print("I will clean your house for you!")

eggs = HomeAndroid("Klara")
eggs.vocalise()
"Beep!"
"I will clean your house for you!"

Множественное наследование

Теперь давайте создадим класс, который наследуется от двухродительских классов:

class Robot:
  def __init__(self, model):
    self.model = model

  def vocalise(self):
      print("Beep!")

  def shut_down(self):
      print("Shutting down...")

class Human:
    def __init__(self, eye_colour):
      self.eye_colour = eye_colour

    def vocalise(self):
        print(f"My eye colour is {self.eye_colour}.")

class Cyborg(Robot, Human):
      def vocalise(self):
          super().vocalise()
          print("I am a cyborg.")

foo = Cyborg("T-1000")
foo.vocalise()
"Beep!"
"I am a cyborg."
foo.shut_down()
"Shutting down..."

Cyborg.mro()
[<class '__main__.Cyborg'>, <class '__main__.Robot'>, <class '__main__.Human'>, <class 'object'>]

Как видите, когда мы вызываем super() из метода vocalise() нашего Cyborg, он просматривает MRO и обнаруживает, что родительский класс Robot предшествует родительскому классу Humanкак мы указали при создании класса Cyborg, поместив Robot класс перед Human классом.

Давайте переключим его:

class Robot:
  def __init__(self, model):
    self.model = model

  def vocalise(self):
      print("Beep!")

  def shut_down(self):
      print("Shutting down...")

class Human:
    def __init__(self, eye_colour):
      self.eye_colour = eye_colour

    def vocalise(self):
        print(f"My eye colour is {self.eye_colour}.")

# I've changed the inheritance order from our previous example:
class Cyborg(Human, Robot):
      def vocalise(self):
          super().vocalise()
          print("I am a cyborg.")

foo = Cyborg("blue")
foo.vocalise()
"My eye colour is blue."
"I am a cyborg."
foo.shut_down()
"Shutting down..."

Cyborg.mro()
[<class '__main__.Cyborg'>, <class '__main__.Human'>, <class '__main__.Robot'>, <class 'object'>]

Если вам понравилась эта статья, подпишитесь на меня в Medium и Twitter. Я часто изучаю модули из стандартной библиотеки, такие как модуль datetime, а также публикую руководства для забавных проектов, таких как создание списка лучших книг научной фантастики с помощью Python.

🐍