OOP in Python — Part 1

Object Oriented Programming in Python 3.x


What is OOP (Object Oriented Programming) ?

Object Oriented Programming is a programming paradigm which creates “objects” during the phase of programming, by this trying to model the entities from the problem which is need to be solved.

The “objects” contain data as form of fields (also called attributes) and have operations defined (called methods). The “objects” have a type definitions, these are called classes in the programming terminology. An “object” is an instance of a class.

There are 3 key things to talk about when we discuss OOP (also called three pillars of OOP):

  • Encapsulation — means locking into one unit the data and the methods which interact with the data within the class. This is useful because the internal structure of a model can only be changed through the methods which it exposes, other entities (objects) cannot modify the internal state of another.
  • Inheritance — usually when we build models about real world problems we end up with large object hierarchies. These models (classes) have relationships among each other. The “is a”relationship in the model hierarchy is implemented using inheritance within OOP, this means features existing in one model (or object) will also exist in the derived one.
    Example: Lets assume you want to build your own web shop and you want to sell gadgets. Gadgets have properties, like battery capacity, weight, amount of memory, operating system and so on. If you want to sell a Tablet in your web shop, you can say, the Tablet is a Gadget, and when implementing these in code you can use inheritance to pass all the attributes and functionalities of the Gadget class to the Tablet class.
  • Polymorphism — is called the possibility of an object to behave in a different way based on the subtype it has. Polymorphism is related to inheritance.
    Example: Lets assume you want to sell smart watches too in your web shop. A Smart Watch is a Gadget. All the gadgets can be restarted, this can be an operation (or method) of a gadget. Since both (Tablet and Smart Watch) inherit from Gadget (they have an is a relationship) they also inherit the restart operation, BUT the restart operation is different on a Smart Watch then in case of a Tablet. So, if we restart both the Smart Watch and the Tablet, we used the common operation (restart) but each of the models executes the operation in its own way, basically, looking at it from a more abstract level we have 2 Gadgets which have a polymorphic behavior (based on their type) when executing the restart operation.

What is Python ?

Python is a dynamic typed, general purpose, high-level programming language. It was designed and developed by Guido van Rossum and released to public in 1991. Nowadays it is very popular and is mostly used in web development along with widespread web frameworks likedjango, flask, bottlepy and pyramid. The name of the language originates from the Monty Python’s Flying Circus which Guido was big fan of.

Which version should I use, 2.x or 3.x ?

The 2.7.x release of Python will be officially supported till 2020. The core development team of the language confirmed there will not be 2.8 version of the language. They will only ship security updates and bug fixes for the 2.x branch. The 3.x branch was released in 2000, it had issues in the beginning, but its very good now. The transition to the new version is hard since most of the linux distributions are still shipped with version 2.x. The main reason behind this is backward compatibility; there are a lot of applications written using branch 2.x and people do not want to break existing applications. The branch 3.x can be easily installed besides 2.x and can be used without any issue.

For a more detailed explanation about differences, pros and cons of Python 2.x and 3.x branches please read these 2 articles:

Classes in Python

As mentioned above, classes are type definitions of objects. The declaration of a class in Python is done using the class keyword. There is a coding convention in python, usually a .py file only contains one class definition. There can be exceptions but this is a good practice to follow. Here is an example of the Gadget class which I talked about earlier:

class Gadget:
weight = 100
operating_system = None
battery_capacity = 2000
screen_size = 1

my_iphone = Gadget()

The Gadget class has 4 attributes (weight, battery_capacity, operating_system and screen_size). We can create new instances of the class using the class name and parenthesis — Gadget(). If take a look at the default values of the class these do not seem to be correct if take into account an iPhone’s technical parameters. We would need a method or a function which would let us specify what are the values for the given instance of the class. The methods which help in creating a new instance of a class are called constructors. Below you can see an implementation of a constructor (__init__) in Python. The __init__ method gets invoked right after the new instance is created. The first parameter of the constructor is called self. This is another code convention. The first parameter represents the object which is newly created new creating the new instance. As you can see we set the attributes of self inside the constructor.

class Gadget:
weight = 100
operating_system = None
battery_capacity = 2000
screen_size = 1

def __init__(self, weight, operating_system, battery_capacity, screen_size):
self.weight = weight
self.operating_system = operating_system
self.battery_capacity = battery_capacity
self.screen_size = screen_size


my_iphone = Gadget(weight = 128, operating_system="iOS", battery_capacity=2800, screen_size=4)

If we want to see what values does our new object have we can print the values:

my_iphone = Gadget(weight = 128, operating_system="iOS", battery_capacity=2800, screen_size=4)

print(my_iphone.weight)
print(my_iphone.operating_system)
print(my_iphone.battery_capacity)
print(my_iphone.screen_size)

There is one problem with this approach, we are violating the Encapsulation rule. We have direct access to the internal state of the my_iphone object, we can simply assign new values to the screen_size or operating_system attributes. The current implementation of the model allows this. We should change these by using properties and hiding the current class members from public access.

class Gadget:
"""A class used for modelling Gadgets in a web shop."""
__weight = 100
__operating_system = None
__battery_capacity = 2000
__screen_size = 1

def __init__(self, weight, operating_system, battery_capacity, screen_size):
self.__weight = weight
self.__operating_system = operating_system
self.__battery_capacity = battery_capacity
self.__screen_size = screen_size

def get_weight(self):
return self.__weight

def set_weight(self, weight):
self.__weight = weight

weight = property(get_weight, set_weight)

@property
def operating_system(self):
return self.__operating_system

@operating_system.setter
def operating_system(self, new_os):
self.__operating_system = new_os

We can hide the attributes(make them private) if we declare them using __[attribute_name]syntax (for more detailed information on these conventions please read the PEP 8 Style Guideline for Python) and we can create setter and getter methods for accessing the private attributes. Using the getter and setter methods is fine, but it provides a Java-like coding experience where class variables can be set with get and set methods. A more developer friendly approach is the use of properties. I created 2 properties (for weight and operating_system), each implemented in different way.

When implementing the property for weight, I used the weight = property(get_weight, set_weight) method to create the weight property. This syntax can be easily applied to python classes where the Java-like get/set method approach was implemented in the first place and using the property(get…, set…) method we can extend the class with properties.

When implementing the property for the operating_system I used another approach, a so called annotation/decorator based. Here, first I defined the get method, but I omitted the get keyword (please note the method operating_system decorated with @property has only one parameter, self); after that I created the setter, which has the same method name (operating_system) but has 2 parameters, the value of self and the new value which needs to be set (called new_os).

>>> from Gadget import Gadget
>>> my_iphone = Gadget(240,'iOS',1980,4)
>>> my_iphone.weight
240
>>> my_iphone.weight = 255
>>> my_iphone.weight
255
>>>
>>>
>>> my_iphone.operating_system
'iOS'
>>> my_iphone.operating_system = 'iOS 8.1'
>>> my_iphone.operating_system
'iOS 8.1'
>>>

Using the properties is fairly easy, here are some examples. Basically properties behave the same way as public members, BUT through the getters and setters we have the option to validate the newly set values (I did not make any validation in this case, but it can be done without any issue).

OOP in Python — Part 2 will come shortly.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.