I want to make multiple inheritance, making a subclass inherit from two different super classes. This subclass should have an __init__
method in which the __init__
methods of the super classes are called with the expected arguments.
Here is an example that can illustrate my problem:
class Place:
def __init__(self, country, city, **attr):
self.country = country
self.city = city
self.attributes = attr
def show_location(self):
print(self.country, self.city)
class Product:
def __init__(self, price, currency = '$'):
self.price = price
self.currency = currency
def show_price(self):
print(self.price, self.currency)
class Flat(Place, Product):
def __init__(self, country, city, street, number, price, currency = '$', **attr):
super(Place).__init__(country, city, **attr)
super(Product).__init__(price, currency)
self.street = street
self.number = number
def show_address(self):
print(self.number, self.street)
myflat = Flat('Mozambique', 'Nampula', 'Rua dos Combatentes', 4, 150000, '$')
But I don't really know how to use the super()
method, and this code throws the following error:
TypeError: super() argument 1 must be type, not str
I wonder how can I initialize the Flat
object both as a Place
and as a Product
...
Is it appropriate to use super()
in this context?
Or is it better to call __init__
directly this way: SuperClass.__init__(self, ...)
?
TL;DR The article Python’s super() considered super! explains in detail how to use super
correctly.
super
is intended to implement cooperative multiple inheritance, where the classes involved are designed together to support such inheritance. This requires redesigning both Place
and Product
slightly; the linked article discusses the rationale for the redesign shown below. (If you can't redesign them, the linked article also explains how to define adaptor classes to wrap them.)
class Place:
def __init__(self, *, country, city, **kwargs):
super().__init__(**kwargs)
self.country = country
self.city = city
def show_location(self):
print(self.country, self.city)
class Product:
def __init__(self, *, price, currency='$', **kwargs):
super().__init__(**kwargs)
self.price = price
self.currency = currency
def show_price(self):
print(self.price, self.currency)
class Flat(Place, Product):
def __init__(self, *, street, number, **kwargs):
super().__init__(**kwargs)
self.street = street
self.number = number
def show_address(self):
print(self.number, self.street)
myflat = Flat(country='Mozambique', city='Nampula', street='Rua dos Combatentes', number=4, price=150000)
The key is that super
refers to the next class in the method resolution order (MRO), a list of classes constructed from the inheritance tree. Only one call to super
is required in each method. Both Place
and Product
use it as well, because neither class knows if it will be the last class in the MRO of the object being initialized. (More precisely, both know they won't, because object
itself is always the final class.)
Each __init__
method accepts a certain number of keyword-only arguments that it knows what to do with, along with arbitrary keyword arguments that it will pass on to one of the ancestor class to handle. Ultimately, if the classes are defined correctly, one of the calls to super().__init__(**kwargs)
will invoke object.__init__
with no keyword arguments, ending the chain.