Python: как инициализировать 2 суперкласса с помощью super()?

У меня есть два суперкласса, отец и мать, и они будут унаследованы ребенком.

class Father:
  def __init__(self, **kwargs):
    self.fathername = kwargs["ffn"] + " " + kwargs["fln"]
    self.fatherage = kwargs["fa"]

class Mother:
  def __init__(self, **kwargs):
    self.mothername = kwargs["mfn"] + " " + kwargs["mln"]
    self.motherage = kwargs["ma"]

Здесь класс Child наследуется от отца и матери

class Child(Father, Mother):
  def __init__(self, **kwargs):
    self.name = kwargs["name"] + " " + kwargs["lastname"]
    self.age = kwargs["age"]

Я могу инициализировать Отца и Мать, если вызову их __init__() по отдельности.

    Father.__init__(self, **kwargs)
    Mother.__init__(self, **kwargs)

Но как добиться того же с помощью super()? Если я назову его, как показано ниже, он инициализирует только Отца, а не Мать (потому что Отец является следующим в MRO, как я предполагаю)

    super().__init__(**kwargs)

Ниже только __str__() переопределено, чтобы показать, что было назначено.

def __str__(self):
    return \
    "Im {}, {} years old".format(self.name, self.age) + "\n" + \
    "My dad is {} and he is {} years old".format(self.fathername, self.fatherage) + "\n" + \
    "My mom is {} and she is {} years old".format(self.mothername, self.motherage)

familyname = "Simpson"
child = Child(**{"name": "Bart", "lastname": familyname, "age": 15, "ffn": "Hommer", "fln": familyname, "fa": 54, "mfn": "Marggie", "mln": familyname, "ma": 46})

Когда я пытаюсь напечатать объект, это не удается, потому что суперкласс Mother никогда не был инициализирован (когда я использовал super() в Child __init__())

print(child)

The program raises a runtime error

Traceback (most recent call last):
  File "/tmp/pyadv.py", line 225, in <module>
    print(child)
  File "/tmp/pyadv.py", line 217, in __str__
    return     "Im {}, {} years old".format(self.name, self.age) + "\n" +     "My dad is {} and he is {} years old".format(self.fathername, self.fatherage) + "\n" +     "My mom is {} and she is {} years old".format(self.mothername, self.motherage)
AttributeError: 'Child' object has no attribute 'mothername'

Итак, как мне использовать super для инициализации двух суперклассов?

РЕДАКТИРОВАТЬ: я попытался добавить super(Father, self).__init__(**kwargs) и super(Mother, self).__init__(**kwargs) в методы суперклассов __init__(), но получил следующую ошибку:

Traceback (most recent call last):
  File "/tmp/pyadv.py", line 225, in <module>
    child = Child(**{"name": "Bart", "lastname": familyname, "age": 15, "ffn": "Hommer", "fln": familyname, "fa": 54, "mfn": "Marggie", "mln": familyname, "ma": 46})
  File "/tmp/pyadv.py", line 217, in __init__
    super().__init__(**kwargs)
  File "/tmp/pyadv.py", line 199, in __init__
    super(Father, self).__init__(**kwargs)
  File "/tmp/pyadv.py", line 208, in __init__
    super(Mother, self).__init__(**kwargs)
TypeError: object.__init__() takes no parameters

Я также пытался добавить super(Father, self).__init__() и super(Mother, self).__init__() (без аргументов внутри __init__()) к методам суперклассов __init__(), но затем получил следующую ошибку:

Traceback (most recent call last):
  File "/tmp/pyadv.py", line 225, in <module>
    child = Child(**{"name": "Bart", "lastname": familyname, "age": 15, "ffn": "Hommer", "fln": familyname, "fa": 54, "mfn": "Marggie", "mln": familyname, "ma": 46})
  File "/tmp/pyadv.py", line 217, in __init__
    super().__init__(**kwargs)
  File "/tmp/pyadv.py", line 199, in __init__
    super(Father, self).__init__()
  File "/tmp/pyadv.py", line 206, in __init__
    self.mothername = kwargs["mfn"] + " " + kwargs["mln"]
KeyError: 'mfn'

Решение 1. Раздевание @blkkngt ниже

Решение 2. Корневой суперкласс, подробно здесь.

class Root:
  def __init__(self, **kwargs):
    pass

class Father(Root):
  def __init__(self, **kwargs):
    self.fathername = kwargs["ffn"] + " " + kwargs["fln"]
    self.fatherage = kwargs["fa"]
    super().__init__(**kwargs)

class Mother(Root):
  def __init__(self, **kwargs):
    self.mothername = kwargs["mfn"] + " " + kwargs["mln"]
    self.motherage = kwargs["ma"]
    super().__init__(**kwargs)

class Child(Father, Mother):
  def __init__(self, **kwargs):
    self.name = kwargs["name"] + " " + kwargs["lastname"]
    self.age = kwargs["age"]
    super().__init__(**kwargs)
  def __str__(self):
    return \
    "Im {}, {} years old".format(self.name, self.age) + "\n" + \
    "My dad is {} and he is {} years old".format(self.fathername, self.fatherage) + "\n" + \
    "My mom is {} and she is {} years old".format(self.mothername, self.motherage)

familyname = "Simpson"
child = Child(**{"name": "Bart", "lastname": familyname, "age": 15, "ffn": "Homer", "fln": familyname, "fa": 54, "mfn": "Marge", "mln": familyname, "ma": 46})

print(child)

person Adriano_Pinaffo    schedule 02.09.2018    source источник


Ответы (2)


Множественное наследование в Python должно быть кооперативным. То есть два родительских класса должны знать о возможности существования друг друга (хотя им не нужно знать какие-либо детали друг друга). Затем тот родитель, который назван первым, может вызвать метод __init__ другого родителя. Вот как работает super, он всегда вызывает следующий класс в MRO (порядок разрешения метода) экземпляра, над которым работает.

В вашем коде сложно сделать это правильно, так как вы всегда передаете полный kwargs dict в своих super вызовах. Это становится проблемой, когда второй родитель пытается вызвать последний класс в MRO, object, который не ожидает получения аргументов ключевого слова. Вместо этого метод __init__ каждого класса обычно должен явно называть параметры, которые он ожидает, и не передавать их снова при вызове super().__init__ (если только он не знает, что одному из его родительских классов тоже нужен аргумент).

Попробуй это:

class Father:
  def __init__(self, ffn, fln, fa, **kwargs): # name the parameters we expect
    super().__init__(**kwargs)           # pass on any unknown arguments
    self.fathername = ffn + " " + fln    # use parameters by name, rather than via kwargs
    self.fatherage = fa

class Mother:
  def __init__(self, mfn, mln, ma, **kwargs):
    super().__init__(**kwargs)
    self.mothername = mfn + " " + mln
    self.motherage = ma

class Child(Father, Mother):
  def __init__(self, name, lastname, age, **kwargs):
    super().__init__(**kwargs)
    self.name = name " " + lastname
    self.age = age

    def __str__(self):
        return \
        "Im {}, {} years old".format(self.name, self.age) + "\n" + \
        "My dad is {} and he is {} years old".format(self.fathername, self.fatherage) + "\n" + \
        "My mom is {} and she is {} years old".format(self.mothername, self.motherage)


familyname = "Simpson"
child = Child(name="Bart", lastname=familyname, age=15, # you can use keyword syntax here
              ffn="Homer", fln=familyname, fa=54,
              mfn="Marge", mln=familyname, ma=46)
print(child)

Обратите внимание, что в Python 3 вам обычно не нужно передавать какие-либо аргументы в super(), он может определить, из какого класса он вызывается, и работать автоматически. В Python 2 вам нужно было указать текущий класс, но это больше не нужно.

Последнее замечание. Хотя я уверен, что ваш код является лишь примером, имена классов очень плохие, когда дело доходит до проектирования ООП. Наследование подразумевает отношение IS-A между двумя классами, что не очень подходит для людей. Например, дочерний элемент, созданный в примере кода (Барт), не является Mother или Father, но в коде сказано, что это так, поскольку он является экземпляром классов Mother и Father. Лучшим способом описания человеческих отношений с родителями будет HAS-A. У каждого ребенка есть мать и отец. Вы можете создавать отношения HAS-A с помощью инкапсуляции. Это означает, что дочерний объект будет иметь ссылку на объект для каждого родителя в атрибуте. Интересно, что это можно сделать только с одним классом (возможно, поэтому вы этого не изучали, если вас учили наследованию):

class Person:
    def __init__(self, firstname, lastname, age, father=None, mother=None):
        self.name = firstname + " " + lastname
        self.age = age
        self.father = father  #  set the attributes for our parents here
        self.mother = mother

fn = "Simpson"
bart = Person("Bart", fn, 15, Person("Homer", fn, 54), Person("Marge", fn, 46))
person Blckknght    schedule 03.09.2018
comment
Ух ты. Это супер круто. Я не был уверен, что такое «обнажение» в статье Реттингера (rhettinger.wordpress.com/2011/05/26/super-considered-super) действительно имел в виду. Теперь я понимаю, что каждый вызов суперкласса удаляет необходимые части dict. И, наконец, объектный класс получает пустой dict. Так что я тоже понял подход Root в его статье и тоже реализовал его. Я редактирую свой вопрос с корневым подходом, если кто-то хочет, в потомстве. Большое тебе спасибо - person Adriano_Pinaffo; 05.09.2018

Из принятого ответа в этой ссылке:

Вызов через super не вызывает всех родителей, он вызывает следующую функцию в цепочке MRO. Чтобы это работало правильно, вам нужно использовать super во всех __init__s

class Parent1(object):
    def __init__(self):
        super(Parent1, self).__init__()
        self.var1 = 1

class Parent2(object):
    def __init__(self):
        super(Parent2, self).__init__()
        self.var2 = 2

class Child(Parent1, Parent2):
    def __init__(self):
        super(Child, self).__init__()
person Community    schedule 02.09.2018
comment
Это не работает. Я редактирую вопрос, чтобы показать ошибку - person Adriano_Pinaffo; 03.09.2018