Object Oriented Thinking in Python

Kirill Bondarenko
13 min readMay 14, 2020

--

How to implement OOP (Object Oriented Programming using Python)

Taken from unsplash

Hello everyone! This time I want to describe such a thing like OOP and how was it implemented in Python 3.x.

The aim of this article is to show that OOP is not hard to understand. It will simplify your work and further development instead of using scripts or just a “bag” of functions. If you are new to programming and OOP this article will help you to understand it in the simplest way.

Introduction

What is OOP in general ?

We need some theory initially in a rough way. After this I will rephrase it and illustrate. But read it and try to understand (it helps).

Object-oriented programming (OOP) is a programming paradigm based on the concept of “objects”, which can contain data, in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods). In OOP, computer programs are designed by making them out of objects that interact with one another. — Wikipedia

Alright, we know that this type of programming style is based on “objects”. Let’s define what is an object in programming.

In computer science, an object can be a variable, a data structure, a function, or a method, and as such, is a value in memory referenced by an identifier. In the class-based and object-oriented programming paradigms, object refers to a particular instance of a class, where the object can be a combination of variables, functions, and data structures. — Wikipedia

So, object is an instance (sample of some bigger party) of a class. Let’s get a definition of a class.

In object-oriented programming, a class is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).[1][2] In many languages, the class name is used as the name for the class (the template itself), the name for the default constructor of the class (a subroutine that creates objects), and as the type of objects generated by instantiating the class; these distinct concepts are easily conflated.— Wikipedia

Now we know that OOP is a programming style based on objects and to create an object we need to write a class.

Visualization and initial coding

Pusheen cat is awesome

To understand OOP we will describe a cat.

First of all, let’s define what our cat can have (properties) ?

  • Name: Pusheen
  • Number of legs: 4
  • Have tail: True
  • Color: Brown
  • Mood: Happy (by default)

Now let’s define it’s behavior (methods). What cats can do ?

  • Sleep
  • Eat
  • Hunt
  • Be nice

Alright. Let’s summarize it into a one diagram.

Cat class

Now we understand what is a class on example of cat. Let’s simple code it in Python.

class Cat:
"""
It is a cat. It's carnivore.
"""

# Now we need to specify a constructor.
# Constructor - is a first method to call after
# class instance creation.
def __init__(self, name, color):
"""
:param name: str(), required to specify while creation
:param color: str(), required to specify while creation
"""
# to create class attribute we need to use 'self'
self.name = name
self.number_of_legs = 4
self.have_tail = True
self.color = color
self.mood = "happy"

# Next we need to add methods to the class
# but we will use pass statement.
# So it means now these methods are useless
# but they will be needed later.
def sleep(self, t):
"""
Method to make cat sleep for t hours
:param t: int(), count of seconds to sleep
:return: void/nothing
"""
pass

def eat(self):
pass

def hunt(self):
pass

def be_nice(self):
pass

Now we have a background for the future work. Before we continue developing class Cat, we need to improve our theory about OOP.

Four horsemen of OOP

Four horsemen of OOP

As you can see on the image above, there are four main principles of OOP and we will investigate each one.

Inheritance

Basically, it is a very cool thing that can make your coding life easy.

For example you have made a class Cat. Now you want to make a class SmallCat with new attribute age. It will be like on the image below.

It means you have no need to duplicate your code, write again all methods and attributes, you just need the next construction below.

class SmallCat(Cat):
"""
Inherited from Cat class(parent)
Additional feature: age
"""

# We need create a constructor with parent arguments 'name' and 'color'.
# And we add there new one 'age'.
def __init__(self, name, color, age):
"""
:param name: str(), name
:param color: str(), color
:param age: float(), age in years
"""
# Now we need to use super() call function to
# initialize parent attributes.
super().__init__(name, color)
# And now we create a new one attribute with self.
self.age = age


cat = Cat(name="Pusheen", color="brown") # create an instance of Cat
small_cat = SmallCat(name="Fluff", color="black", age=0.2) # instance of SmallCat

cat.eat()
cat.be_nice()

small_cat.eat()
small_cat.hunt()
print(small_cat.age)
# 0.2
print(small_cat.have_tail)
# True

You see that SmallCat has all the attributes like ‘have_tail’ from Cat and has its own one ‘age’. Also it has all the methods from Cat class. It’s easier than rewrite the whole class. Just inherit.

Encapsulation

Cool word, yea. But what is it ?

Encapsulation — is a way to secure or to hide some part of your class attributes/methods.

There are three types of encapsulation in OOP. I will add examples right here in the listing below.

  • Public — when your object can be reached from any point of code. In Python in implemented pretty simple: just do nothing with your variable like at the example below.
class Example:
def __init__(self):
self.variable = None


example = Example()
v = example.variable
  • Protected — type of encapsulation when you have access to certain attribute/method only inside the package (group of files). In Python you can reach it by simple adding _ to the start of the name.
class Example:
def __init__(self):
self._variable = None

def _protected(self):
return 0


example = Example()
v = example._variable
example._protected()
  • Private — type of encapsulation when only members of the class can reach this attribute/method. In Python it’s implemented by double _ (__) before the name.
class Example:
def __init__(self):
self.__variable = None
self.__private()

def __private(self):
self.__variable = 10


example = Example()

So in the lat one example we see that we can change __variable only inside the class. But how we can call protected/private attributes/methods properly from the outside of the class ?

In Python we have some kind of “getters” (c-lang hello). So now I’ll show the example of properly calling protected/private attributes/methods. We will need a property.

Property — is a decorator.

Decorator — is a pattern in Python that can expand a method functionality without even changes in its code. If you see this pattern for the first time, better read about it here.

class Example:
def __init__(self):
self.__private_variable = 100
self._protected_variable = 10

@property
def private_variable(self):
return self.__private_variable

@property
def protected_variable(self):
return self._protected_variable


example = Example()
print(example.private_variable)
# 100
print(example.protected_variable)
# 10

So your “getter” must have the same name as your private/protected variable but just without starting double _ .

But what sense to do it ? You made something private inside the class and now just beautifully ruined it by property decorator.

Here is the thing. Now you can specify how exactly you can reach private data of your class. For example, you can set a password.

class Example:
def __init__(self):
self.__private_variable = 100
self.__password = None

def enter_login(self, password):
self.__password = password

@property
def private_variable(self):
if self.__password == "x":
return self.__private_variable
else:
return "No permission"


example = Example()
print(example.private_variable)
# No permission
example.enter_login("x")
print(example.private_variable)
# 100

Now we can improve our Cat class like this.

class Cat:
"""
It is a cat. It's carnivore.
"""

def __init__(self, name, color):
"""
:param name: str(), required to specify while creation
:param color: str(), required to specify while creation
"""
self._name = name
self._number_of_legs = 4
self._have_tail = True
self._color = color
self._mood = "happy"

@property
def name(self):
return self._name

@property
def number_of_legs(self):
return self._number_of_legs

@property
def have_tail(self):
return self._have_tail

@property
def color(self):
return self._color

@property
def mood(self):
return self._mood

def sleep(self, t):
"""
Method to make cat sleep for t hours
:param t: int(), count of seconds to sleep
:return: void/nothing
"""
pass

def eat(self):
pass

def hunt(self):
self._mood = "aggressive"

def be_nice(self):
self._mood = "happy"

Now we encapsulated our class. Also here were added implementations of methods ‘hunt’ and ‘be_nice’ that can change attribute ‘mood’.

Let’s see an example.

cat = Cat(name="Pusheen", color="brown")
print(cat.mood)
# happy

Now we want to somehow change the mood of cat. But in OOP paradigm we can’t just call the attribute by property and change it like on example below.

cat = Cat(name="Pusheen", color="brown")
cat.mood = "aggressive" # do not do this !
# AttributeError: can't set attribute
print(cat.mood)

The right way to change your cat mood is to run properly actions !

cat = Cat(name="Pusheen", color="brown")
cat.hunt() # cat hunting and being aggressive !
print(cat.mood)
# aggressive
cat.be_nice() # so now it's just a fluffy ball
print(cat.mood)
# happy

Also here is important to investigate such thing like static variables and methods.

A static method is a method that is shared by all instances of a class. And they can be private or public. If they are public then you can call them without the need to instantiate the classes. It mostly used static methods in the helper classes or utils classes.

The same with static variable/attribute of a class. It can be used without instantiating a class. How to implement it in Python ?

class Example:
name = "Example" # just create a variable outside the init

# to create a static method use decorator @static
# and do not use self in it
@staticmethod
def static():
print(Example.name)


class Example2(Example):
name = "Example2"


class Example3(Example):
name = "Example3"

@staticmethod
def static():
print(Example3.name)

print(Example.name) # Example
Example.static() # Example
Example2.static() # Example
Example3.static() # Example3

Polymorphism

Again one more cool word to your personal dictionary. What is it ?

I think, it is the best image to illustrate it.

Basically, polymorphism — is a technique to make different things by using only one function.

For example you have a car. You can drive it 24/7 with no exceeding speed limit in your country and it’s all. But another one person who has totally same model of car can race/drift by streets or rob a bank etc. Well, basically same car unit can get totally different results by only changing its usage parameters like driving style, gas pedal pushing force etc. It’s called polymorphism.

Now we can come closer to programming.

There are two ways to make polymorphism in our code.

  • Overriding— when a child of class inherits its method and extends its functionality.

For example we will override “function” that basically returns doubled value now will return squared value.

class Example:
def __init__(self):
self.attribute = 1

def function(self, value):
return value * 2


class ExampleChild(Example):
def function(self, value):
return value * value


example = Example()
print(example.function(10))
# 20
child = ExampleChild()
print(child.function(10))
# 100
  • Overloading — when you have a method in your class with some set of parameters. In case of usage/filling different parameters you will have different results.
class Example:
def __init__(self):
pass
def function(self, value, param=None):
if param is not None:
if isinstance(param, int):
return value * param
elif isinstance(param, str):
return param + str(value)
else:
return value * 10


example = Example()
print(example.function(10))
# 100
print(example.function(10, 35))
# 350
print(example.function(7, "hello"))
# 'hello7'

Now we cant implement polymorphism to our cat. Let’s change it’s method ‘eat’. Method will have parameter food(string) and parameter additional_food(string)=None. If food = “meat” cat’s mood will be happy, if food = “vegetables”, cat’s mood will be sad. But in case we set additional food != None cat’s mood will be happy no matter what food equals to.

class Cat:
"""
It is a cat. It's carnivore.
"""

def __init__(self, name, color):
"""
:param name: str(), required to specify while creation
:param color: str(), required to specify while creation
"""
self._name = name
self._number_of_legs = 4
self._have_tail = True
self._color = color
self._mood = "happy"

@property
def name(self):
return self._name

@property
def number_of_legs(self):
return self._number_of_legs

@property
def have_tail(self):
return self._have_tail

@property
def color(self):
return self._color

@property
def mood(self):
return self._mood

def sleep(self, t):
"""
Method to make cat sleep for t hours
:param t: int(), count of seconds to sleep
:return: void/nothing
"""
pass

def eat(self, food, additional_food=None):
if additional_food is not None:
cat._mood = "happy"
else:
if food == "meat":
cat._mood = "happy"
elif food == "vegetables":
cat._mood = "sad"

def hunt(self):
self._mood = "aggressive"

def be_nice(self):
self._mood = "happy"


cat = Cat(name="Pusheen", color="brown")
print(cat.mood)
# happy
cat.eat(food="meat")
print(cat.mood)
# happy
cat.eat(food="vegetables")
print(cat.mood)
# sad
cat.eat(food="vegetables", additional_food="fish")
print(cat.mood)
# happy

Abstraction

Now we come to the interesting and very important part of the OOP paradigm. What is abstraction ?

Abstraction — is a way to hide inner complexity of your program by covering it in simple interfaces.

For example a car. Does usual driver need to know how engine works and how much fuel just been burned in this second to move piston in it for certain elevation ? — No. Driver must know how to turn the key/press the ‘start’ button and how to turn a rudder and press pedals. Also driver must now driving rules :) But not a car under the hood things except of filling glass cleaning liquid in right hole.

So the same even with a cat. Do we need to know how cat’s heart beats and what processes run inside cat’s brain to just pet him/her or feed ? — Also no.

And finally the same with your code. You need to provide very simple interface to your client/business who doesn’t need to know inner complex equations to run the code.

Here let’s create another example of program. We will make a simple calculator and it’s interface.

We want to make a console simple app with four main functions: addition, subtraction, division and multiplication.

First of all we need a core. But before core we will define our Function class. It must be simple.

class Function:
def __init__(self, name, function):
self.__name = name
self.__function = function

@property
def name(self):
return self.__name

@property
def function(self):
return self.__function

Exactly this class will be an argument to our core that we make next.

class Core:
def __init__(self, *args):
self.__functions = args
self.__exe_functions = dict()
self.__build_dict()

def __build_dict(self):
for function in self.__functions:
self.__exe_functions[function.name] = function.function

def get_function(self, key):
return self.__exe_functions.get(key)

It will receive variable number of arguments of Function instances and write to private attribute “self.__exe_functions” as a Python dict.

Now we can build our core. So we will make a CoreBuilder function. It’s also pretty simple. We define here four our main functions and give them names like we want them call in future.

class CoreBuilder:
def __init__(self):
self.__addition = Function(
name="add",
function=lambda a, b: a + b
)
self.__subtraction = Function(
name="sub",
function=lambda a, b: a - b
)
self.__division = Function(
name="div",
function=lambda a, b: a / b)
self.__multiplication = Function(
name="mul",
function=lambda a, b: a * b)
self._core = Core(
self.__addition,
self.__subtraction,
self.__division,
self.__multiplication
)

@property
def core(self):
return self._core

We have a built core and functions that we need to call to perform operations. Let’s test it before we make interface.

builder = CoreBuilder()
a = 10
b = 56
name = "add"
function = builder.core.get_function(name)
result = function(a, b)
print(result)
# 66

Now let’s cover it into Executor class.

class Executor:
def __init__(self, core):
self.core_builder = core

def execute(self, name, a, b):
function = self.core_builder.core.get_function(name)
if function is not None:
return function(a, b)
else:
return "No matching command"

Great, we almost have done it. Now we need to combine all parts together in Calculator class. This class needs to create CoreBuilder, Executor and three parameters like variable “a”, “b” and command/function name to execute. But we need to parse the line (string) to retrieve these parameters and fill them. So before calculator we need a Reader class.

class Reader:
def __init__(self):
self.__a = 0
self.__b = 0
self.__command = ""

def read_data(self, string):
self.__a, self.__command, self.__b = string.split(" ")
self.__a = int(self.__a)
self.__b = int(self.__b)

def get_data(self):
return self.__a, self.__command, self.__b

And now we will combine it all together in Calculator.

class Calculator:
def __init__(self):
self._builder = CoreBuilder()
self._executor = Executor(self._builder)
self._reader = Reader()
self.command = None
self.a = 0
self.b = 0

def perform_operation(self, string):
self._reader.read_data(string)
self.a, self.command, self.b = self._reader.get_data()
print(self._executor.execute(self.command, self.a, self.b))

And finally we will cover it in a simple interface called Runner.

class Runner:
def __init__(self, run=False):
self.calculator = Calculator()
if run:
while True:
print("Enter your command like this")
string = input()
if string == "exit":
break
else:
self.calculator.perform_operation(string)

Finally, our abstraction is done. Look at the the visualization below.

We fit complex program into a simple interface where definitely obvious: need to calculate — write string. How ? helping message with example here. That’s all that user must know about your program and even will not know about builders, executors etc.

Conclusion

Now we can say that briefly we passed the OOP in Python. But is it efficient in comparison with other languages and what advantages and disadvantages it has.

Advantage is its simplicity. It’s pretty obvious in case of the whole Python language simplicity. You can simply define classes, its methods and attributes and encapsulate them by simple _ symbols.

Well, here that’s all. Suddenly, C-group of languages have more possibilities to implement good OOP.

For example encapsulation. If in C++ you make some variable private, you have no access to it. What in python ? There is one trick.

class Example:
def __init__(self):
self.__variable = 10


example = Example()
print(example._Example__variable)
# 10

So be happy to write beautiful code in python and develop your OOP skills, but remember that Python OOP is different from classic C paradigm.

Hope, you have found something new and interesting in this article.

Good luck!

With best wishes,

Bondarenko K., machine learning engineer;

--

--

Kirill Bondarenko

Young and positive thinking machine learning engineer, living in Ukraine.