Python Object Oriented programming tips

Vivekkumar Muthukrishnan
Analytics Vidhya
Published in
6 min readApr 11, 2021

This is a continuation to my previous article on things to do when writing a library or framework with Python that would make the code more maintainable and generic for extension.

Please check out my first post if not already.

Right of bat, let’s look at the problem statement.

  1. What if we are writing a library or a wrapper on top of a framework, where we are adding more functionalities to it but we would also want to make use of its underlying features without re implementing it.
  2. How to add extra capabilities for a class without modifying it or inheriting from it? Basically we do not want to add tight coupling between classes.

Let’s discuss how to tackle these issues one by one.

Intercepting the “ . ” while calling instance methods

For the first issue the idea is to intercept the ‘.’ when we call a method of an instance to forward it to super class’s implementations. Consider the below example.

class Person(object):
def __init__(self, first_name, last_name, address):
self.first_name = first_name
self.last_name = last_name
self.address = address

@classmethod
def create_using_full_name(cls, full_name, address):
name = full_name.split(' ')
return cls(name[0], name[1], address)

class Employees(object):
def __init__(self):
self.employees_list = []

def __len__(self):
return len(self.employees_list)

def __getitem__(self, item):
return self.employees_list[item]

@classmethod
def add_employee_to_list(cls, list_of_emps=[]):
self = cls()
for emp in list_of_emps:
self.employees_list.append(emp)
return self

def print_emp_name(self):
return [person.first_name + person.last_name for person in self.employees_list]
person1 = Person(“Henry”, “Thiery”, “France”)
person2 = Person(“Xaxi”, “Hernandez”, “Spain”)
person3 = Person(“Erling”, “Haaland”, “Sweden”)
employees = Employees.add_employee_to_list([person1, person2, person3])
employees_list.print_emp_name()
[‘HenryThiery’, ‘XaxiHernandez’, ‘ErlingHaaland’]

Here employees is a List like object but not a List. Suppose we want to have append methods of List object available in Employees object we need to inherit from List object and override append method in the Employee class definition which is tight coupling and difficult to maintain if we want to support more methods from List object.

employees = Employees.add_employee_to_list([person1, person2, person3])
len(employees)
3
employees[1]
<__main__.Person object at 0x10fd987c0>
employees[1].first_name
‘Xaxi’

There is a solution for this though, we can define a magic method __getattr__ which intercepts the ‘.’ when we call an instance method and we can redirect it to the appropriate class using the getattr method which we saw in the previous post.

class Employees(object):
def __init__(self):
self.employees_list = []

def __len__(self):
return len(self.employees_list)

def __getitem__(self, item):
return self.employees_list[item]

def __getattr__(self, item):
return getattr(self.employees_list, item)

@classmethod
def add_employee_to_list(cls, list_of_emps=[]):
self = cls()
for emp in list_of_emps:
self.employees_list.append(emp)
return self

def print_emp_name(self):
return [person.first_name + person.last_name for person in self.employees_list]

Now the employee object magically has the methods of a List object, it is actually being redirected to the main class because we had intercepted the ‘.’ using getattr. Now onto our second problem.

employees = Employees.add_employee_to_list([person1, person2, person3])
employees.append
<built-in method append of list object at 0x10fd88200>
employees.sort
<built-in method sort of list object at 0x10fd88200>
employees.index(1)
employees.index
<built-in method index of list object at 0x10fd88200>

Using Mixins to reduce tight coupling

We use mixins to solve our next problem which is kind of a multiple inheritance. People who code in frameworks like Django or Flask would be surely using it or at least would have heard about it.

I saw a good stackoverflow answer on mixins. Pasting it here,
Mixins are used When you want to provide optional inheritance to a class.
Like you want to reuse one particular feature in different types of classes.
And I agree, in our example below we use it for the above mentioned points and as a side product we are reducing tight coupling and on our way of achieving to write generic code.
Let’s consider this Person class again as an example,

class Person(object):
def __init__(self, name, age, sex, profession):
self.name = name
self.age = age
self.sex = sex
self.profession = profession

Let’s say we want to print the contents of the objects of the class in a table format by choosing which field we want from the instance. We write below function to achieve that.

def print_table(objects, attrs):
for attr_name in attrs:
print(attr_name, end='\t')
print()
for obj in objects:
for attr_name in attrs:
print(getattr(obj, attr_name), end="\t")
print()
name age profession
Thiery Henry 42 Footballer
Hernandez Xaxi 35 Footballer
Erling Haaland 21 Footballer

(Please pardon the indenting for time being) Now the output looks ok. But what if want to have different output formats similar to text, like csv, html etc.
Changing the code of print_table to accept any object which has a heading and contents printing method.

def print_table(objects, attrs, format):
format.heading(attrs)
for obj in objects:
rows = [str(getattr(obj, attr_name)) for attr_name in attrs]
format.contents(rows)

Now let us implement out Formatter code, which we would pass as a parameter to print_table method.
So, this is a base class sort of a blue print. And we inherit the base class and implement blueprint.

class Formatter(object):
def __init__(self, save_file=None):
if save_file:
self.save_file = save_file

def heading(self, headers):
raise NotImplementedError

def contents(self, rows):
raise NotImplementedError


class TextFormatter(
Formatter): # if i want to support width param or add a feature then I would need to init parent and create my var

def __init__(self, save_file=None, width=20):
self.width = width
super().__init__(save_file=save_file)

def heading(self, headers):
for header in headers:
print(header, end='\t')
print()

def contents(self, rows):
print()
for row in rows:
print({}.format(row), end="\t")


class CVSFormatter(Formatter):
def heading(self, headers):
for header in headers:
print(header, end=',')
print()

def contents(self, rows):
for row in rows:
print('{}'.format(row), end=",")
print()


class HTMLFormatter(Formatter):
def heading(self, headers):
for header in headers:
print('<h>{}</h>'.format(header), end=' ')
print()

def contents(self, rows):
for row in rows:
print('<t>{}</t>'.format(row), end=" ")
print()
persons = [person1, person2, person3]# Creating a formatter
formatter = CVSFormatter()
print_table(persons, [‘name’, ‘age’, “profession”], format=formatter)
# Creating another formatter
formatter = HTMLFormatter()
print_table(persons, [‘name’, ‘age’, “profession”], format=formatter)
# output of each formatter
name,age,profession,
Thiery Henry,42,Footballer,
Hernandez Xaxi,35,Footballer,
Erling Haaland,21,Footballer,
<h>name</h> <h>age</h> <h>profession</h>
<t>Thiery Henry</t> <t>42</t> <t>Footballer</t>
<t>Hernandez Xaxi</t> <t>35</t> <t>Footballer</t>
<t>Erling Haaland</t> <t>21</t> <t>Footballer</t>

Seems our code works. But there are some problems. What if we would want to add a functionality of adding quotes to the rows. Be it any formatter text, CSV or HTML.

And suppose if we want to add support of width param or add any other param to TextFormatter then we would need to init the parent and if parent has some parameter that needs to be initialised with init which the child doesn’t care about it. This is tight coupling.

Then we have to make so many changes from the parent to child classes, And what if we would want to have to add this as a parameter to the child class?

class TextFormatter(Formatter):
def __init__(self, save_file=None, width=20):
self.width = width
super().__init__(output_type=None)

Again below is another example of tight coupling, we are inheriting from TextFormatter which we don’t need to.

class QuoterMixin(TextFormatter):
def contents(self, rows):
quoted = ['"{}"'.format(text) for text in rows]
super().contents(quoted)

Remember why we need mixins? This is one of the reasons. We do not want tight coupling to add a functionality to our class, we just create a mixin class that just adds it to the contents method and calls the super class’s contents method.

class QuoterMixin(object):
def contents(self, rows):
quoted = ['"{}"'.format(text) for text in rows]
super().contents(quoted)

Now we create a new formatter object which inherits from two classes. And does what we need it to do.

class Formatter(QuoterMixin, CVSFormatter):
pass


formatter = Formatter()
person1 = Person(“Thiery Henry”, “42”, “M”, “Footballer”)
person2 = Person(“Hernandez Xaxi”, “35”, “M”, “Footballer”)
person3 = Person(“Erling Haaland”, “21”, “M”, “Footballer”)

persons = [person1, person2, person3]
print_table(persons, [‘name’, ‘age’, “profession”], format=formatter)
formatter = CVSFormatter()
print_table(persons, [‘name’, ‘age’, “profession”], format=formatter)
formatter = HTMLFormatter()
print_table(persons, [‘name’, ‘age’, “profession”], format=formatter)
name,age,profession,
“Thiery Henry”,”42",”Footballer”,
“Hernandez Xaxi”,”35",”Footballer”,
“Erling Haaland”,”21",”Footballer”
<h>name</h> <h>age</h> <h>profession</h>
<t>”Thiery Henry”</t> <t>”42"</t> <t>”Footballer”</t>
<t>”Hernandez Xaxi”</t> <t>”35"</t> <t>”Footballer”</t>
<t>”Erling Haaland”</t> <t>”21"</t> <t>”Footballer”</t>

And now double quotes magically appear in the rows like how we wanted. We have successfully added a feature and also the code is not tightly coupled.
That concludes this post. I hope these tips would help you in someway or the other when you are coding in Python. Especially these tips helped us to create a more cleaner framework on top of Apache Spark for our use case. Thanks for reading my article.

[1]: Python Cookbook: Recipes for Mastering Python 3 3rd Edition

https://www.amazon.in/Python-Cookbook-Recipes-Mastering-ebook/dp/B00DQV4GGY

--

--

Vivekkumar Muthukrishnan
Analytics Vidhya

Hi I am Vivek. Software Engineer solving distributed and data problems and active music listener from Bach to the Beatles and every artist in between.