I am starting to learn the Python language and need a critique of my code as well as solution to an error message I am getting.
The code creates a Robot class with two ancestors; Transformer and Gundam. A third class Hybrid inherits from these. I can instantiate a Transformer and a Gundam without error. Instantiating a Hybrid returns an error. Code and error follow below:
Main.py
from multiple_inheritance import Robot, Transformer, Gundam, Hybrid
tito = Gundam("Tito", "Japan")
optimus = Transformer("Optimus Prime", "Mars")
Jaeger = Hybrid("Striker", "US")
print(tito)
print(optimus)
multiple_inheritance
class Robot(object):
"""A robot class"""
def __init__(self, name):
self.name = name
def __str__(self):
return "{name}".format(name = self.name)
class Transformer(Robot):
"""A transformer"""
def __init__(self, name, planet):
self.planet = planet
super(Transformer, self).__init__(name)
def __str__(self):
return "Name = {name}, Planet = {planet}".\
format(name = self.name, planet = self.planet)
class Gundam(Robot):
"""A Gundam"""
def __init__(self, name, country):
self.country = country
super(Gundam, self).__init__(name)
def __str__(self):
return "Name = {name}, Country = {country}".\
format(name = self.name, country = self.country)
class Hybrid(Transformer, Gundam):
"""Ultimate Robot"""
Error Message
Traceback (most recent call last):
File "D:\Tech\Python\my_code\unit10\multiple_inheritance_main.py", line 5, in <module>
Jaeger = Hybrid("Striker", "US")
File "D:\Tech\Python\my_code\unit10\multiple_inheritance.py", line 13, in __init__
super(Transformer, self).__init__(name)
TypeError: __init__() missing 1 required positional argument: 'country'
2 Answers 2
Hybrid's mro looks something like this:
>>> Hybrid.__mro__
(<class '__main__.Hybrid'>, <class '__main__.Transformer'>, <class '__main__.Gundam'>, <class '__main__.Robot'>, <type 'object'>)
As you can see after Transformer the next class is Gundam and you're not passing enough arguments to it when calling it from Transformer's __init__:
class Transformer(Robot):
"""A transformer"""
def __init__(self, name, planet):
self.planet = planet
#####This calls Gundam's __init__
super(Transformer, self).__init__(name)
So, the point is super() calls the next class in MRO not Transformer's base class as you expected.
A very good post related to super(): Python’s super() considered super!
3 Comments
Transformer(Robot) now calls Gundam as super? Then if you create a plain Transformer, how can you tell it which parent to call?__init__ - the one in Transformer, or the one in Gundam?super() is bit complicated, Yes for the current MRO super call from Transformer will call Gundam. I really don't understand your second question, the parent can be identified using the class's MRO.As pointed in the other answer, Python builds a MRO (method resolution order) for your class, placing Hybrid->Transformer->Gundam->Robot - that means not only that missing methods and attributes are searched in the classes in this order, but that is the order used when calling methods using the "super" construct.
I don't know of anyone ever saying multiple inheritance was "easy". It was even left altogether out of the Java language due to the complexities that arise. Python makes then usable with the "super" construct and the MRO concept - but if you need different keywords in the initializers of different superclasses, each class has to know how to deal with the arguements it does not know about.
So Hybrid is to be called with both a "name", a "country" and a "planet" argument - but Transformers don't know about "country", nor Gundams know about "planet".
In Python you can handle that using Keyword arguments - you have to make your intermediary classes accept all kinds of keyword arguments, and use the ones it knows about - in this case, it is as simply as changing the classes' __init__ signature in this way, and make the proper adjust to the super calls:
class Robot(object):
"""A robot class"""
def __init__(self, name, **kwargs):
...
class Transformer(Robot):
"""A transformer"""
def __init__(self, name, planet, **kwargs):
...
super(Transformer, self).__init__(name, **kwargs)
class Gundam(Robot):
"""A Gundam"""
def __init__(self, name, country, **kwargs):
...
super(Gundam, self).__init__(name, **kwargs)
...
And of course, when instatiating an Hybrid, you now have to name the parameters:
my_robot = Hybrid("XYZ-Star", planet="Earth", country="Japan")
2 Comments
Transformer and Gundam classes handle name as a keyword argument, since they don't use it themselves at all. That means, either use name=name when passing it on, or simply not taking it as a named parameter in the first place (so it will be part of kwargs).
Robot class with two ancestorsincorrect. In your code,Robotis the ancestor of bothTransformerandGundam