Search code examples
pythonclassinheritancemultiple-inheritance

multiple inheritance without defining a class


I'm not sure that's possible, and I'm wondering if that's the way to go, or if there exists a better way of doing that.

I'll create a toy example to expose what I'm looking for.

Suppose I have people of different nationalities. I'm creating a class for each nationality and this classes all have a method greeting.

class French:
  def greeting(self):
    print("Bonjour")

class English:
  def greeting(self):
    print("Hello")

class Spanish:
  def greeting(self):
    print("Hola")

Of course this could be done having a single class Person and using conditionals inside the method greeting, but we can imagine the function greeting being more complex than that and justifying having a class for each Nationality.

You could also imagine all this Nationalities being in reality a child class of a class Person and overriding the method greeting to output their own language.

Now, imagine I another feature, like "Sport" which then I also create different classes for the different sports.

class Football:
  def sport(self):
    print("Football")

class Tennis:
  def sport(self):
    print("Tennis")

class Golf:
  def sport(self):
    print("Golf")

Again, instead of having a single class Sport, imagine the function sport is more complex, and different classes are justified. Or we we can imagine all this classes being children of a general class Sport and overriding the method sport.

Now, I want a class that inherits both from a Nationality and a Sport. This class will not add new methods or attributes, it just inherits from two classes, so it has it's own greeting and sport.

I can create a class FrenchFootball with inherits both from French and Football.

class FrenchFootball(French, Football):
  pass

I can now instantiate this class and it will have both a method greeting and a method sport

foo = FrenchFootball()
foo.greeting()   # Bonjour
foo.sport()      # Football

Now, here's the question:

The child class, doesn't need to add any attributes or methods, it just links a Nationality with a Sport so the resulting instance says Bonjour and likes Football.

However, in order to create such an instance, I have to define a class FrenchFootball that just inherits both classes.

If I want all combinations of this toy example I need to define 9 child classes, so I can instantiate them by their name

Question:

Is there a way I can instantiate foo to be an English Tennis without having to code a class EnglishTennis?

You can imagine that having 10 sports and 20 Nationalities makes creating those child link classes a nightmare.

I would like something like (pseudocode)

foo = new Class(English, Tennis)

That basically tells python: I want an instance of an empty class that inherits English and Tennis, but I don't really have coded an EnglishTennis class. I'm defining the inheritances when instantiating the class.


Solution

  • If one is using the class statement, then, as you observe, classes have to be hardcoded in a source file.

    Python, however, allows for the dynamic creation of classes. One just have to call the built-in type, with the class name as the first parameter, the bases as second, and a (possibly empty) namespace as the third:

    new_class = type("FrenchFootball", (French, Football), {})
    

    So, in this case, you can simply use something like itertools.Products to create all possible bases combinations for your dynamic classes, and use those to create the class-names as well, and put those into a dictionary!

    from itertools import product
    
    class French:
        ...
    
    ...
    
    class Football:
        ...
    
    ...
    
    all_classes = {}
    for bases in product([French, English, ...], [Football, Basketball, Golf, ...]):
         name = bases[0].__name__ + bases[1].__name__
         all_classes[name] = type(name, bases, {})
    

    And there it is. Perceive that it may not make sense to add the dynamic created classes to the global module scope, as any code using the class would have to hardcode the classname anyway: retrieving them from the all_classes dictionary allow for dynamic usage. But if you want to, it is just a matter of updating the dict returnd by a globals() call:

    globals().update(all_classes)