In my previous post I explained that objects in python are really just special wrappers for dictionaries. There is one dictionary that contains the attributes of the object and another dictionary that contains the attributes of the class. Indeed we can really just think of the __init__
method as a static method that adds elements to a dictionary.
Objects in Python do however have one feature that you cannot get by just passing around dictionaries: inheritance.
Let’s suppose we are using some Arbitrary Object-Oriented Language. This language looks and works a lot like C#, Java or C++. We define two classes, Security
and Bond
in this language. Security has a single field, an integer, called Id
:
public class Security
{
public int Id;
public Security(int id)
{
Id = id;
}
}
Bond inherits from Security and adds another field, this time a double called Rate
:
public class Bond : Security
{
public double Rate;
public Bond(int id, double rate) : super(id)
{
Rate = rate;
}
}
What do we get by making Bond
a subclass of Security
? The most obvious thing we get is that Bond
will get a copy of the implementation inside Security
. So in this example, as Security
has a public field called Id
, Bond
has one as well. We can think of Security
as a user defined type that is built up out of primitive types. By using inheritance Bond
extends this type.
In Python we can do something similar. First we define our two classes:
class Security:
def __init__(self, id):
self.id = id
and:
class Bond(Security):
def __init__(self, id, rate):
self.rate = rate
Security.__init__(self, id)
Now, this time when we define Bond
as a subclass of Security
what do we get? Python objects are not composite types like in our object oriented language. In Python objects are really just dictionaries of attributes and these attributes are only distinguished by their names. So our bond class could have a numeric value in it’s Rate
attribute, but it could also have a string value, or a list or any other type. When we subclass in python we are not extending a user defined type, we are just re-using some attribute names.
In our Arbitrary Object Oriented language, there is another advantage to making Bond
a subclass of Security
: the ability to treat Bond
as a Security
. To understand what this means, suppose we have a function that prints the ids of a list of Securities:
static void printPortfolio(Security[] securities)
{
string ids = "";
foreach(Security security in securities)
{
ids += (" " + security.id);
}
Console.WriteLine(ids);
}
Now, this function specifies it’s single parameter must be an array of Securities. However, by the magic of polymorphism, we can actually pass in an array of types that inherit from Security. When they are passed in they are automatically cast to type Security
. This can be pretty useful, in particular it makes it a lot easier to reason about our code.
Now let’s define the same function in Python:
def print_portfolio(securities):
ids = ""
for security in securities:
ids += (" " + str(security.id))
return ids
On the face of it, this is very similar. However we are not really using polymorphism in the same way as we are in our object oriented language. We could actually pass a list of any objects into the print_portfolio
function, and, as long as they had a id
attribute, this would execute happily. We could, for example, define a completely unrelated class like so:
class Book:
def __init__(self, id):
self.id = id
and pass a list of these into our print_portfolio
function without any problems. Indeed in Python we can dynamically add attributes to an object, so we could even create an empty class:
class Empty:
def __init__(self):
pass
and assign a id
attribute to it at runtime, via:
e = Empty()
e.id = "Hello"
and then enclose it in a list [e]
and pass it into the print_portfolio
function.
There’s one more thing we get with inheritance in an object oriented language: access to protected members. When we mark a method or field as protected it will only be accessible from within objects of that type or types that inherit from it. However in Python there are no protected methods or fields, everything is public.
So there are three reasons why I don’t think inheritance makes sense in Python:
- Python classes aren’t really composite types, so it doesn’t make sense to extend them
- Inheritance doesn’t give us extra access to protected methods, as everything in python is public anyway
- Inheritance in Python doesn’t give us the benefit of polymorphism because in Python there are really no restrictions on what objects we pass around
So, that is why there really is no extra benefit to using an object in Python rather than a dictionary.