Adriano_Pinaffo
Adriano_Pinaffo

Reputation: 1699

Python: how to initialize 2 superclasses using super()?

I have two superclasses, Father and Mother and they will be inherited by Child.

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"]

Here class Child inherits from Father and Mother

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

I can initialize Father and Mother if I call their __init__()s separately.

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

But how do I achieve the same with super()? If I call it like below, it only initializes Father and not Mother (because Father is the next one in the MRO I assume)

    super().__init__(**kwargs)

Below is just the __str__() overridden to show what was assigned.

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})

When I try to print the object, it will fail because Mother superclass was never initialized (when I used super() in the 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'

So, how do I use super to initialize two superclasses?

EDIT: I tried to add super(Father, self).__init__(**kwargs) and super(Mother, self).__init__(**kwargs) to the superclasses __init__() methods but I got the following error:

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

I also tried to add super(Father, self).__init__() and super(Mother, self).__init__() (no arguments inside __init__()) to the superclasses __init__() methods but I then got the following error:

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'

Solution 1: @blkkngt strip-off below

Solution 2: Root superclass, detailed here.

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)

Upvotes: 1

Views: 1007

Answers (2)

Blckknght
Blckknght

Reputation: 104762

Multiple inheritance in Python needs to be cooperative. That is, the two parent classes need to be aware of the possibility that each other exist (though they don't need to know any of each other's details). Then whichever parent is named first can call the other parent's __init__ method. That's how super works, it always calls the next class in the MRO (the method resolution order) of the instance being operated on.

Your code makes it hard to do this correctly, as you're always passing the full kwargs dict in your super calls. That becomes a problem when the second parent tries to call the last class in the MRO, object, which doesn't expect to receive any keyword arguments. Instead, each class's __init__ method should usually explicitly name the parameters it expects, and not pass them on again when it calls super().__init__ (unless it knows that one of its parent classes needs the argument too).

Try this:

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)

Note that in Python 3, you usually don't need to pass any arguments to super(), it can figure out which class it is being called from and work automatically. In Python 2 you had to specify the current class, but that's not needed any more.

A final note. While I'm sure that your code is only an example, the names of the classes are very poor when it comes to OOP design. Inheritance implies an IS-A relationship between the two classes, which isn't really appropriate for people. The child created in the example code for instance (Bart) is not a Mother or a Father, but the code says that he is, since he is an instance of both the Mother and Father classes. A better way to describe human relationships with parents would be HAS-A. Each child has a mother and a father. You can make HAS-A relationships using encapsulation. That means, the child object would have a reference to an object for each parent in an attribute. Interestingly, that can be done with just one class (which is probably why you weren't learning this, if you're being taught about inheritance):

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))

Upvotes: 2

user10287394
user10287394

Reputation:

From the accepted answer in this link:

Invocation via super doesn't call all the parents, it calls the next function in the MRO chain. For this to work properly, you need to use super in all of the __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__()

Upvotes: 0

Related Questions