A comprehensive practical guide in the light of object oriented programming

Class is the most fundamental piece of Python. The reason lays behind the concept of object oriented programming.

Everything in Python is an object such as integers, lists, dictionaries, functions and so on. Every object has a type and the object types are created using classes.

Classes have:

  • Data attributes: Define what is needed to create an instance of a class
  • Methods (i.e. procedural attributes): Define how to interact with the instances of a class

Methods are just like functions but they belong to a particular class. The attributes can be considered as an interface to interact with a class.

An advantage of classes is that we do not need to know how it is created. We can just use it through data attributes and methods. For instance, in order to use the linear regression model from scikit-learn, we just import the LinearRegression class.

from sklearn.linear_model import LinearRegression

We just need to know how to use or interact with the LinearRegression class. We are not interested in how it is created. This is the idea of abstraction. The behavior is defined but implementation is hidden (You can see it if you want to).

This post can be considered as a comprehensive introduction to classes. We will go over many examples that explain features of them. There will also be some tips to keep in mind when implementing a class.

We will also do a few examples that explain inheritance which is a very important concept in object oriented programming.

Creating a Class

Let’s start by creating a simple class called “Person”.

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

The __init__ is a special method that automatically runs when an instance of class is created. The parameters represent the data attributes. Self is the instance itself. You can use any word instead of “self” but it is a highly common practice to use “self”.

The age and name are the other two data attributes. Thus, each instance of the Person class has age and name data attributes.

We create an instance of the Person class.

p1 = Person(24, "John")print(type(p1))
<class '__main__.Person'>

print(p1)
<__main__.Person object at 0x7f67faac9ba8>

The type prints out the type (i.e. class) of an object. Thus, p1 is an instance of type Person.

When we print p1, Python returns the type and the memory location of the object. However, we can change it by implementing the __str__ method in our class.

Let’s make it print the name and age of the person.

class Person():
def __init__(self, age, name):
self.age = age
self.name = name def __str__(self):
return "<" + self.name + "," + str(self.age) + ">"

If we print an object of type Person now, we will get the name and the age.

print(p1)
<John,24>

We can access the data attributes of an object as below:

p1 = Person(24, "John")print(p1.name)
Johnprint(p1.age)
24

We can also change the attributes in the same way:

p1 = Person(24, "John")print(p1.age)
24p1.age = 28print(p1.age)
28

However, this approach is not recommended. Instead, we can implement methods to get and set the attribute values (aka getters and setters).

Let’s do it for the age attribute:

def set_age(self, age):
self.age = agedef get_age(self):
return self.age

Note: I’m only writing the new parts instead of the writing entire class definition each time.

The set_age method updates the age of a person with the given value. The get_age method just returns the value of age attribute. The self parameter indicates the instance itself.

p2 = Person(26, "Emily")print(p2.get_age())
26p2.set_age(27)print(p2.get_age())
27

We called the methods on the object. Another option is to call it on the class.

p2 = Person(26, "Emily")print(Person.get_age(p2))
26Person.set_age(p2, 27)print(Person.get_age(p2))
27

If you want to use this option, make sure to pass the name of the object inside the method.

In order for these methods to return a value, we need to use the parenthesis. Otherwise, you will a get message like this:

p1 = Person(36, "Edward")print(p1.get_age)
<bound method Person.get_age of <__main__.Person object at 0x7fe33bf08eb8>>

We can define methods for our class. Remember the methods are just like functions but associated with a particular class.

Let’s define a method that calculates the age difference between two person objects.

def age_diff(self, other):
diff = self.age - other.age
return abs(diff)

It takes two objects and returns the absolute value of the age difference between them:

p1 = Person(22, "Ashley")
p2 = Person(26, "Max")print(p1.age_diff(p2))
4print(Person.age_diff(p1, p2))
4

Creating a Child Class (Inheritance)

We will create another class which is based on the Person class. In object oriented programming, there is a concept called inheritance.

It is similar to the inheritance in real life. Most of our genome come from our parents. We inherit from them. Thus, we have similarities with our parents.

The inheritance works the same way with classes. When we create a child class, it inherits the attributes (both data and procedural) from the parent class. However, we are free to add or override these attributes.

Let’s create a new class called MarriedPerson. It will be the child class of Person. In addition to the age and name data attributes, it will have spouse name and number of children attributes.

Here is the class definition including the __init__ method:

class MarriedPerson(Person):
def __init__(self, age, name, spouse_name, number_of_children=0):
Person.__init__(self, age, name)
self.spouse_name = spouse_name
self.number_of_children = number_of_children

Two important points here:

  1. The name of the parent class in written in parenthesis so that python knows the MarriedPerson class inherits all attributes of the Person class.
  2. Since the age and name attributes have already been defined in the Person class, we can just copy the __init__ method of the Person class. We just need to define the additional attributes.

Note: We are free to define each data attribute manually for the child class. Using the __init__ of parent is optional.

Note: We can also inherit the attributes from the parent class in the __init__ method as below:

class MarriedPerson(Person):
def __init__(self, age, name, spouse_name, number_of_children=0):
super().__init__(age, name)
self.spouse_name = spouse_name
self.number_of_children = number_of_children

We can now create a MarriedPerson object (an instance of this class).

mp1 = MarriedPerson(26, 'Max', 'Ashley', 2)print(mp1)
<Max,26>

As you can see, the print function only prints the name and age of the married person. The reason is that the MarriedPerson class inherits the __str__ method from the Person class.

We can override it.

class MarriedPerson(Person):
def __init__(self, age, name, spouse_name, number_of_children):
Person.__init__(self, age, name)
self.spouse_name = spouse_name
self.number_of_children = number_of_children def __str__(self):
return "<" + self.name + "," + "married to " +
self.spouse_name + ">"

If we print a MarriedPerson object now, we will see name and spouse name.

mp1 = MarriedPerson(26, 'Max', 'Ashley', 2)print(mp1)
<Max,married to Ashley>

We created a method that returns the age difference between two Person objects (age_diff). Since we inherit from the Person object, we can use that method with the MarriedPerson objects.

mp1 = MarriedPerson(26, 'Max', 'Ashley', 2)
mp2 = MarriedPerson(29, 'Emily', 'John', 1)print(mp1.age_diff(mp2))
3

We can specify default values for data attributes. For instance, a married person may not have a child. Thus, we can set the default values as 0.

class MarriedPerson(Person):
def __init__(self, age, name, spouse_name, number_of_children=0):
Person.__init__(self, age, name
self.spouse_name = spouse_name
self.number_of_children = number_of_children

Unless specified otherwise, the number_of_children attribute will get the value 0.

mp1 = MarriedPerson(26, 'Max', 'Ashley', 2)
mp2 = MarriedPerson(29, 'Emily', 'John')print(mp1.number_of_children)
2print(mp2.number_of_children)
0

Here is the final version of the Person and MarriedPerson classes:

Person class (image by author)
MarriedPerson class (image by author)

Conclusion

There is much more to cover about Python classes. For instance, the difference between class variables and instance variables is an important topic.

However, what we covered in this post will get you comfortable with working and creating classes. It can be considered as a comprehensive introduction.

As you keep practicing, you will learn much more in the way.

Thank you for reading. Please let me know if you have any feedback.

Author

Writing about Data Science, AI, ML, DL, Python, SQL, Stats, Math | linkedin.com/in/soneryildirim/ | twitter.com/snr14