3

I'm learning multiple inheritance in Python3. I'm wondering why the case# 1 works but case# 2 doesn't. Here are my code snippets.

class ContactList(list):
    def search(self, name):
        """Return all contacts that contain the search value
        in their name."""
        matching_contacts = []
        for contact in self:
            if name in contact.name:
                matching_contacts.append(contact)
        return matching_contacts


class Contact:
    all_contacts = ContactList()

    def __init__(self, name="", email="", **kwargs):
        super().__init__(**kwargs)
        self.name = name
        self.email = email
        Contact.all_contacts.append(self)
        print("Cotact")


class AddressHolder:
    def __init__(self, street="", city="", state="", code="", **kwargs):
        super().__init__(**kwargs)
        self.street = street
        self.city = city
        self.state = state
        self.code = code
        print("AddressHolder")


class Friend(Contact, AddressHolder):
    # case# 1
    # def __init__(self, phone="", **kwargs):
    #     self.phone = phone
    #     super().__init__(**kwargs)
    #     print("Friend")

    # case# 2
    def __init__(self, **kwargs):
        self.phone = kwargs["phone"]
        super().__init__(**kwargs)
        print("Friend")


if __name__ == "__main__":
    friend = Friend(
        phone="01234567",
        name="My Friend",
        email="[email protected]",
        street="Street",
        city="City",
        state="State",
        code="0123")

Here is the out put of the script. If I use only kwargs (case# 2) as a parameter in "Friend" that inherits "Contact" and "AddressHolder" then Python throughs error. I already tested the same type of construct without inheritance, except object, that worked perfectly.

"""
case 1#
$ python contacts.py
AddressHolder
Cotact
Friend
>>> friend.name
'My Friend'
>>> friend.phone
'01234567'
>>>
"""

"""
case 2#
$ python contacts.py
Traceback (most recent call last):
  File "contacts.py", line 55, in <module>
    code="0123")
  File "contacts.py", line 43, in __init__
    super().__init__(**kwargs)
  File "contacts.py", line 16, in __init__
    super().__init__(**kwargs)
  File "contacts.py", line 25, in __init__
    super().__init__(**kwargs)
TypeError: object.__init__() takes no parameters
>>>
"""

1 Answer 1

5

Let’s take a look at the method resolution order for Friend. This will tell us in what order the constructors will be called:

>>> Friend.mro()
[<class '__main__.Friend'>, <class '__main__.Contact'>, <class '__main__.AddressHolder'>, <class 'object'>]

So let’s take a look at case 1 first. These are the original named arguments to the Friend constructor:

phone="01234567"
name="My Friend"
email="[email protected]"
street="Street"
city="City"
state="State"
code="0123"

Now, Friend.__init__ takes phone as an explicit argument and then takes the rest as a keyword argument dictionary. so kwargs is the following:

kwargs = {
    'name': "My Friend",
    'email': "[email protected]",
    'street': "Street",
    'city': "City",
    'state': "State",
    'code': "0123",
}

Next, Contact.__init__ is called which takes the arguments name and email. This leaves kwargs like the following:

kwargs = {
    'street': "Street",
    'city': "City",
    'state': "State",
    'code': "0123",
}

Next, AddressHolder.__init__ is called which takes the arguments street, city, state, and code. So kwargs is left as the following:

kwargs = {}

An empty dictionary! So the final call to object.__init__ happens without passing any arguments, which is good because the object constructor does not accept any.

Now, let’s take a look at the second case now. Here, phone is not an explicit parameter of Friend.__init__, so it is passed as a keyword argument inside the dictionary. In all subsequent calls, the flow is exactly as above, except that phone is never taken out of the kwargs dictionary. So with the final call to object.__init__, kwargs will still look like this:

kwargs = {
    'phone': "01234567",
}

And that’s a problem for object.__init__ because it does not accept a phone argument (or any really). So that’s why case two will fail here: Because there’s a remaining keyword argument that gets carried until object.__init__.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you @poke for the nice explanation. Now things are getting more clear to me. Couldn't upvote because of my low reputation. However, this answer definitely changed my way of learning Python.
Please note that even calling object's __init__ with empty kwargs like {} is not allowed!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.