Python language basics 80: polymorphism through interfaces
February 20, 2016 Leave a comment
Introduction
In the previous post we started looking at one of the most important ideas of object oriented design: polymorphism. Polymorphism is a way to represent common features through objects. The implementation details of the related functions through those objects do not matter to the caller. E.g. all vehicles can be accelerated. In cars you press the gas pedal, you pedal faster on a bicycle and do something else on aeroplanes. The exact acceleration process is an implementation detail of the Car, Bicycle and Aeroplane classes probably represented by a function called “accelerate” that accepts some numeric argument, such as “how_fast”.
In this post we’ll look at interface type polymorphism in Python.
Interfaces
I have to confess that the title of this post is misleading. There are no interfaces in Python, at least not in the same strict programmatic sense as you can declare interfaces in C#, Java etc. You would then implement those interfaces in various classes by some keyword such as “implements” in Java. There’s none of that Python. In fact you don’t even need classes to achieve something similar. Functions are also objects in Python and are often just enough to express polymorphism. Therefore it’s slightly different from what you’ve seen in those strictly typed languages.
We’ll look at a formatting example. When printing out the properties of an object you can have multiple formats: XML, Json, HTML, whatever.
We’ll look at a Person object that consists of several other objects: a Name, an Address, a Telephone and an age. We can print the properties of the Person object in various ways as mentioned before. However, we don’t want the Person object to worry about implementation details of the formatting itself. We don’t want to give the Person object the responsibility to know the rules and details of XML, Json, HTML, CSV etc. It’s a better design choice to let the Person object accept a function and delegate the formatting to that function. The Person object will only be responsible of passing along the properties that need to be printed by the formatter. Hopefully this process will be clearer as we go along.
OK, let’s start with the Name, Address and Telephone objects. They are all very simple with no validation to keep them short. You can put them in a Python file domains.py:
class Name: def __init__(self, first_name, last_name, middle_name): self._first_name = first_name self._last_name = last_name self._middle_name = middle_name def name(self): return self._first_name + " " + self._middle_name + " " + self._last_name class Address: def __init__(self, street_name, street_number, postal_code, city): self._street = street_name self._number = street_number self._postal_code = postal_code self._city = city def address(self): return str(self._number) + " " + self._street + " " + self._postal_code + " " + self._city class Telephone: def __init__(self, home, office, mobile): self._home_number = home self._office_number = office self._mobile_number = mobile def home_number(self): return self._home_number def mobile_number(self): return self._mobile_number def office_number(self): return self._office_number
Let’s look at the formatters. We’ll create 2 rudimentary implementations. Python can handle JSON and XML in other libraries but that’s beside the point. Consider the following functions also in domains.py:
def print_as_xml(root_name, properties_dictionary): print("<" + root_name + ">") for prop in properties_dictionary: print("<" + prop + ">" + properties_dictionary[prop] + "</" + prop + ">") print("</" + root_name + ">") def print_as_json(root_name, properties_dictionary): lines = [] print("{\"" + root_name + "\": {") for prop in properties_dictionary: lines.append("\"" + prop + "\": \"" + properties_dictionary[prop] + "\"") print(",".join(lines)) print("}}")
Probably the single most important detail here is that both functions are void and accept the same type of parameters: a root name and a dictionary with all the properties we want to print. You’ll see that we print the root name followed by the property keys and values in a simple JSON and XML tree. Also, keep in mind that functions are also objects in Python and we can pass them around as input parameters.
Here’s the Person class in domain.py which demonstrates the usage of the formatter in the format_me function:
class Person: def __init__(self, name, address, telephone, age): self._name = name self._address = address self._telephone = telephone self._age = age def format_me(self, formatter): properties = {"name": self._name.name(), "age": str(self._age), "address": self._address.address()} formatter("person", properties)
We collect the object properties in a dictionary and then delegate the actual formatting to the formatter that was passed into the format_me function. The Person knows nothing about the actual implementation of the formatter function. It’s only important the it must have two parameters otherwise the code breaks. This is how interfaces are adhered to in Python. It’s done very implicitly without the usage of specialised interfaces. If you send in a formatter function that does not accept two parameters then the interface is broken and the code throws an exception.
Let’s see how it can be used:
from domains import * name = Name("John", "Smith", "William") telephone = Telephone("34535", "345345", "65765754") address = Address("New street", 34, "324 56", "Los Angeles") person = Person(name, address, telephone, 30) person.format_me(print_as_xml)
We construct a Person object and call its format_me function. We pass in the print_as_xml function. Again, keep in mind that we pass in a function which will then be invoked in the format_me function of the Person class. The above code produces the following result:
<person> <address>34 New street 324 56 Los Angeles</address> <age>30</age> <name>John William Smith</name> </person>
It’s simple to switch from XML to JSON:
person.format_me(print_as_json)
…which produces the following result:
{"person": {
"address": "34 New street 324 56 Los Angeles","age": "30","name": "John William Smith"
}}
Note that we didn’t have to change anything within the Person class to make this switch. Instead, we just changed the implementation details. An additional benefit is that the print_as_xml and print_as_json functions can be reused in other objects that also require formatting.
Read the next post here.
Read all Python-related posts on this blog here.