Who Should Read This Post?¶
TL;DR: Non programmers or newbie programmers
For professionals who use Python as a scripting language rather than a full-fledged programming language, investing time to learn programming basics such as Object Oriented Programming (OOP) may seem unappealing or even unnecessary. Working as a Data Scientist I can vouch for a large group of DS out there that a lot of us feel that way!
But learning OOP programming can become very rewarding and a precious efficiency booster as soon as you find yourself putting your solutions in production.
This post will be part of a series of posts discussing OOP in Python. In this post, without going too much into the guts of OOP I will try to set up some background about what is OOP, portray a simplistic picture to show you why you may want to learn OOP.
A Very Functional Definition of OOP¶
Object Oriented Programming (OOP) is,
A method of modeling programming around object rather than function or logic.
By object one can think of a unit or block that comes with some unique characteristics (attributes) and capabilities (methods).
For anyone with some familiarity with coding but starting their journey with OOP, object may seem like an abstract concept. To make this transition a bit easier let's work through an example. At least, for me, usually an example helps out more than words. So before going further into defining other concepts of OOP let's try to walk through an example to see what is actually an object and how is it different than writing a plain program as we know it.
Let's think about a simple scenario. Assume that you have a client/user who is looking for a way to create an empty box. And then to have the ability to add numbers to that box and remove the last number if needed.
How would you do that in the simplest way? To simplify let's assume the requirement of box can be a simple list.
If you have dabbled in Python for a bit, you would likely create a step by step solution like this:
- Create an empty list.
- Then define one function to enable the user to add number to the list.
- Define another function for removal of the last added number from the list.
In fact, this is what we most typically do in cases of iterative process such as data wrangling and exploration in the fields such as data science/analystics.
A possible solution may look like this:
num_list =  # function to add value to the list def add_value(val): num_list.append(val) # function to remove value from the list def remove_value(): removed_val = num_list[-1] del num_list[-1] return removed_val
print('Initial list of values:', num_list) add_value(5) add_value(8) add_value(5.5) add_value(10) print('Updated list after adding values to it:', num_list) val = remove_value() print('Updated list after removing value %s is: ' % (val), num_list)
Initial list of values:  Updated list after adding values to it: [5, 8, 5.5, 10] Updated list after removing value 10 is: [5, 8, 5.5]
This step by step method of programming is called "Procedural Programming".
For a simple case, like the one in the example, where you have full control over the empty list such procedural codes work just fine. But some complications may arise as your project gets more collaborative and complicated.
Your future self or your co-author may unintentionally modify the list (
num_list) in an unintended way i.e. adding or removing numbers to the list without using the functions defined. For example, they may insert a value in the first place like this:
num_list = 5rather than only at the last place.
Or if you need to create multiple similar empty lists in the future, you would have to re-write these steps for each of the use cases. While thinking about it also think about what would happen if you had 50 functions instead of just two! For 5 different cases, you would have to write total of 250 functions!
⛑️ In such cases OOP comes to the rescue!
Object Oriented Programming (OOP)¶
Rather than writing down the steps, one defines a kind of a skeleton with the needed features and abilities or methods.
Such skeleton is then re-used to create objects in the future as needed.
So, in our naive client solution scenario if we consider the procedural codes as a product what are the features that it has?
- It contains an empty list.
- It gives a method to add value at the end of the list.
- It gives another method to remove the last item from the list.
So we will encapsulate these features inside a block which is called Class, consider a class as a container of features and capabilities.
Now let's define our class for our product and call it NumList. Don't worry about new syntaxes for now. We will come back to them later. For now, rather focus on how an object is structured e.g. functions are defined.
class NumList: def __init__(self): self.__list =  def add_value(self, val): self.__list.append(val) def remove_value(self): rv = self.__list[-1] del self.__list[-1] return rv def print_list(self): return self.__list
list01 = NumList() print('Initial list of values of list01:', list01.print_list()) list01.add_value(2) list01.add_value(20) list01.add_value(44) list01.add_value(12) print('Updated list after adding values to it:', list01.print_list()) val = list01.remove_value() print('Updated list after removing value %s is: ' % (val), list01.print_list())
Initial list of values:  Updated list after adding values to it: [2, 20, 44, 12] Updated list after removing value 12 is: [2, 20, 44]
It's doing exactly what we achieved using procedural programming. So why go through all these extra hurdles?
Because now your product:
list01, an instance of class
NumListis more secure. Try changing value, or any other modifications, in
list01without using any of the defined methods and you'll encounter an error.
Creating subsequent instances of the same kind has become a breeze! Rather than declaring an empty list and defining each function separately, now you can just declare an instance of the class NulList and the instances inherit the methods. Check below code blocks to see how a second and third list is created from NumList class below.
list02 = NumList() print('Initial list of values list02:', list02.print_list()) list02.add_value(2000) list02.add_value(500) list02.add_value(44444) list02.add_value(122222) print('Updated list after adding values to it:', list02.print_list()) val = list02.remove_value() print('Updated list after removing value %s is: ' % (val), list02.print_list())
Initial list of values:  Updated list after adding values to it: [2000, 500, 44444, 122222] Updated list after removing value 122222 is: [2000, 500, 44444]
list03 = NumList() print('Initial list of values list03:', list03.print_list()) list03.add_value(0.1555) list03.add_value(0.333) list03.add_value(0.444) list03.add_value(0.555) print('Updated list after adding values to it:', list03.print_list()) val = list03.remove_value() print('Updated list after removing value %s is: ' % (val), list03.print_list())
Initial list of values list03:  Updated list after adding values to it: [0.1555, 0.333, 0.444, 0.555] Updated list after removing value 0.555 is: [0.1555, 0.333, 0.444]
Hopefully, by now OOP is a bit less ambiguous to you and you begin to see the value of investing time in programming in an object oriented way. To keep it simple, I will end this post here and leave the explanations of the elements of a class for a separate follow-up post.
In the next post, we will go over the class that we have created to explain the coding syntaxes, elements of a class and expand on the class to make it richer with features and methods.