class Parent1:
    def method(self):
      print("Parent1")
class Parent2:
    def method(self):
      print("Parent2")
class Child(Parent1, Parent2):
  pass
c = Child()
c.method() # Выведет Parent1
Parent1.method(self) внутри метода дочернего класса.super():  super().method(). super() обращается к следующему классу в MRO.  Важно использовать super() согласованно во всех классах.class Child(Parent1, Parent2)), влияет на MRO.class Parent1:
    def method(self):
      print("Parent1")
      super().method() # Важно для корректной работы super()
class Parent2:
    def method(self):
      print("Parent2")
class Child(Parent1, Parent2):
    def method(self):
      print("Child")
      super().method()
c = Child()
c.method() # Выведет Child, Parent1, Parent2 (в этом порядке)
Множественное наследование - это возможность для класса наследовать атрибуты и методы от нескольких родительских классов. Это мощный инструмент, но он может привести к проблемам, особенно с "конфликтом имен" (также известным как "diamond problem").
class A:
    def method(self):
        print("Method from A")
class B:
    def method(self):
        print("Method from B")
class C(A, B):
    pass
instance = C()
instance.method()  # Вывод: Method from A
В этом примере класс C наследует от A и B. Оба класса имеют метод с одинаковым именем method. Python использует Method Resolution Order (MRO) для определения, какой метод будет вызван.
MRO - это порядок, в котором Python ищет метод в иерархии классов. В Python 3 используется алгоритм C3 linearization для построения MRO. Его цель – обеспечение предсказуемого и логичного порядка поиска методов.
Можно посмотреть MRO класса с помощью атрибута __mro__ или метода mro():
print(C.__mro__) # Вывод: (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
print(C.mro())  # Вывод: [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
В данном случае MRO показывает, что Python сначала будет искать метод в классе C, затем в A, затем в B и, наконец, в object.
Существует несколько способов решения проблемы конфликта имен:
C (например, class C(B, A):).  Однако, это может привести к неожиданному поведению и усложнить поддержку кода. Это обычно не рекомендуется, если нет веских причин.
class C(A, B):
    def method(self):
        A.method(self)  # Вызов метода класса A
        # Или
        B.method(self)  # Вызов метода класса B
super(): super() позволяет вызывать методы родительских классов, соблюдая MRO.  Это предпочтительный способ решения проблемы diamond problem.
      
class A:
    def method(self):
        print("Method from A")
        super().method() # Вызов метода следующего класса в MRO
class B:
    def method(self):
        print("Method from B")
class C(A, B):
    pass
instance = C()
instance.method()
# Вывод:
# Method from A
# Method from B
В этом примере super().method() в классе A вызывает метод класса B, поскольку B является следующим классом в MRO.
Важно отметить, что использование super() требует, чтобы классы были правильно спроектированы для совместной работы. Это часто требует использования кооперативного множественного наследования.
Множественное наследование - мощная, но сложная концепция.  Важно понимать MRO и выбирать подходящий подход для разрешения конфликтов имен.  В большинстве случаев, использование super() или композиции является предпочтительным способом.