Virtual Subclassing¶
Python Virtual subclassing¶
Unlike some other languages, python supports the concept of virtual subclassing
. What exactly
is virtual subclassing? To understand the concept let’s go back to pythons roots. I am sure
you are familiar with the concept of duck typing and perhaps if you have came from another
language such as java
, you will be familiar with the concept of an interface
. In python
we are not overly caught up on the type of X, but more so how X behaves, in the simplest
form if it walks like a duck and quacks like a duck, chances are its a duck. Python does not support
an official interface
argument, however more recently Protocols
can be @runtime_checkable
to aid with catching issues early.
To understand why we may need virtual subclassing, let’s take a simple example. Firstly we are tasked with developing an airplane tycoon game. Our game consists of multiple things that can fly, to use the age old cliche:
A Bird
An Aeroplane
Turns out, you cannot load people into A Bird and fly them to a new destination, so we develop a few abstract base classes to easily distinguish them
from abc import ABC from abc import abstractmethod class Plane(ABC): @abstractmethod def fly(self): ... # We make a simple plane class MyPlane(Plane): def fly(self): print("Plane preparing to fly!") # Now we can check in our library function # Note: isinstance, isinstance checks against an ABC are even questionable.. def generate_flight(plane: Plane): if not isinstance(plane, Plane): raise TypeError("Not a Plane!") plane.fly() # Now, if we accidentally receive a Bird, we will handle the case # This will allow us to handle it gracefully rather than potentially # Blow up with some other weird errors later on in the program class Bird: def fly(): print("bird flying!") generate_flight(Bird()) """ TypeError Traceback (most recent call last) <ipython-input-16-be9dc02ec41e> in <module> ----> 1 generate_flight(Bird()) <ipython-input-15-d59f577c3fde> in generate_flight(plane) 18 def generate_flight(plane: MyPlane): 19 if not isinstance(plane, MyPlane): ---> 20 raise TypeError("Not a Plane!") 21 plane.fly() 22 TypeError: Not a Plane! """ generate_flight(MyPlane()) # Plane preparing to fly!
Excellent, but whats the point in virtual subclassing still? Let’s say in the near future some other
fantastic library comes along with a dozen of cool new planes, the problem is our generate_flight()
function here is strictly prohibiting non explicit subclasses of Plane
. Let’s take a look at
the library code
# snippet from another library, not developed by you # boeing.py class Boeing747: def fly(self): print("Boeing 747 preparing for travel!") def repair(self): ...
Pretty simple huh, a nice shiny new Boeing that we could use in our system, except of course it does not adhere to our explicit abstract base class:
from boeing import Boeing747 generate_flight(Boeing747()) """ TypeError Traceback (most recent call last) <ipython-input-23-24437f4c4918> in <module> ----> 1 generate_flight(Boeing747()) <ipython-input-20-8038f0273ec8> in generate_flight(plane) 18 def generate_flight(plane: MyPlane): 19 if not isinstance(plane, MyPlane): ---> 20 raise TypeError("Not a Plane!") 21 plane.fly() 22 TypeError: Not a Plane! """
Damn, we have coupled our library a little too tight and we don’t own the library code, what gives?
Rather than monkey patching and hacking around the inheritance of Boeing747
, enter virtual subclassing
.
We can simple register the third party code as a virtual subclass of our _interface_ (abstract base class) and
the python interpreter will treat it like it has actually subclassed it.
from boeing import Boeing747 Plane.register(Boeing747) isinstance(Boeing747(), Plane) # True! issubclass(Boeing747, Plane) # Also True! generate_flight(Boeing747()) # Boeing 747 preparing for travel!
Voila, we have successfully used third party code and ackknowledged explicitly that we accept it is an adaquate implementation of our interface.
Note: virtual subclassing should be used extremely sparingly, in reality the need for it is often miniscule. It is also possible to automatically consider objects as instance/subclasses based on their interface and python does this internally a lot in its collections.abc module, more on that in a separate post alter.