kavian@home:~$

Virtual Subclass in Python

As you are into cars like me, you are writings a program to drive a car autonomously. You created a superclass Car that is an abstract class with an abstract method drive that concrete subclasses would implement like the Benz class:

import abc


class Car(abc.ABC):
    @abc.abstractmethod
    def drive(self): ...

    def info(self):
        print('4 wheel vehicle')


class Benz(Car):
    def drive(self):
        print("Driving in Benz")

You can easily check whether a class is a subclass or instance of the Car class by using issubclass and isinstance:

>>> issubclass(Benz, Car)
True
>>> isinstance(Benz(), Car)
True

Now imagine you have installed a library that has a Tesla class and has the drive method just like our Car class:

class Tesla:
    def drive(self):
        print("Driving in Tesla")
    

We have a function that drives a car autonomously by getting a car instance as a parameter and calling its drive method; if the parameter isn’t an instance of the Car it will raise an exception:

def autonomous_driving(car: Car):
    if isinstance(car, Car):
        car.drive()
    else:
        raise Exception("Car not supported")

Tesla library will not work with your existing code, because the Tesla has not inherited from the Car:

>>> issubclass(Tesla, Car)
False
>>> isinstance(Tesla(), Car)
False
>>> autonomous_driving(Benz())
'Driving in Benz'
>>> autonomous_driving(Tesla())
Exception: Car not supported

For solving this first thing that comes to mind is to change the Tesla signature by inheriting from the Car. This is possible when you wrote the Tesla class, but Tesla is an external library and it is installed via pip; so you can’t change its signature.

Virtual Subclass

Now Virtual Subclass into help. By making Tesla, a ‘virtual subclass’ of the Car class it will be recognized as Car’s subclass.

We have to register the Tesla as a ‘virtual subclass’ of the Car in order for our autonomous_driving function to work fine:

Car.register(Tesla)

By making Tesla a virtual subclass of the Car the issubclass and isinstance will return True:

>>> issubclass(Tesla, Car)
True
>>> isinstance(Tesla(), Car)
True

Now our autonomous_driving function will work:

>>> autonomous_driving(Tesla())
'Driving in Tesla'

One thing that we should keep in mind is that by making a class virtual subclass, it can not call the superclass methods and attributes, which means that superclass methods don’t show up in the virtual subclass MRO (Method Resolution Order).

Here we are calling the info method of the Car class; because the Tesla is not the subclass of the Car (it is his ‘virtual subclass’) it will not inherit its methods and attributes.

>>> Benz().info()
'4 wheel vehicle'
>>> Tesla().info()
AttributeError: 'Tesla' object has no attribute 'info'

MRO - Method Resolution Order

MRO is the sequence that python takes to find a method in the class hierarchy and python uses the C3 Linearization Algorithm since python 2.3.

Imagine you have these classes that class D has multiple inheritance from the class B and class C:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

Multiple Inheritance

To get the MRO of a class we can call its mro method or its __mro__ attribute:

>>> D.mro()
[<class 'virtual_class.D'>, <class 'virtual_class.B'>, <class 'virtual_class.C'>, <class 'virtual_class.A'>, <class 'object'>]

Here python checks class B before class C because in the definition of class D, class B comes before class C. Object is always the last class because every class in the python inherits from it.

Refrences

  1. https://docs.python.org/3/library/abc.html#abc.ABCMeta.register
  2. http://masnun.rocks/2017/04/15/interfaces-in-python-protocols-and-abcs/