Kubernetes with OpenShift World Tour: Get hands-on experience and build applications fast! Find a workshop!

Object-oriented programming in Python

Object-oriented programming (OOP) is a programming paradigm based on the concept of objects, which can contain data in the form of attributes and code in the form of methods. Another definition of OOP is a way to build flexible and reusable code to develop more advanced modules and libraries such as Numpy and Pandas.

Everything in Python is an object. For instance, string and list are Python objects. A class is like a blueprint for creating objects. It is easy to maintain code that is written using OOP techniques because of the modular structure. Programs are more secure with the encapsulation approach used in OOP.

This tutorial discusses the fundamentals of object-oriented programming such as classes, objects, methods, polymorphism, and inheritance to give you a good foundation in OOP.

Class

The present data structures in Python like integers and strings are designed to represent something like the number of employees and the name of an employee. A class in OOP is like a blueprint for objects, and you can develop your own data structure by using OOP. For example, you could create an Employee class to track properties about the employee, such as the name or salary.

The class keyword is used to create a class in Python. The class name follows the class keyword followed by the colon. The body of the class starts on a new line and is indented one tab from the class keyword.

Constructor is a method that is called by default whenever you create an object from a class. You must create a method with keyword init to create a constructor. In the following example, I create a class named Employee with two class attributes status and number_of_employee as well as two instance attributes employee_id and name. The Employee class also contains the give_info() method.

class Employee:

    #class attributes
    status = "active"
    number_of_employee = 0

    def __init__(self, employee_id, name):
        self.employee_id = employee_id #instance attribute
        self.name = name #instance attribute
        Employee.number_of_employee += 1

    #class method
    def give_info(self):
        print("Name:",self.name,"\nID:",self.employee_id)

The first employee_id in the self.employee_id = employee_id expression is an instance attribute or variable, and the second employee_id is a parameter. The same thing is also true for the name variable. When creating an object using a class, you can write emre = Employee("101", "Emre Kutluğ"), 101 and Emre Kutluğ are arguments.

Object

As I said earlier, a class is like a blueprint for creating objects. The relationship between a class and an object can be understood by looking at the relationship between an animal and Yogi Bear. Yogi Bear is an animal. An animal is an abstract concept. It is implemented in the form of Yogi Bear or Mickey Mouse. Therefore, we need to create an object of a class before we can use its methods and attributes.

An object is also called an instance. Therefore, the process of creating an object of a class is called instantiation. In Python, to create an object of a class, you must write the class name followed by opening and closing parenthesis. In the following example, I create an object of Employee class.

emre = Employee("101", "Emre Kutluğ")

I can use the type method to check the type of the object. As you see in the following example, the type of emre object is a class Employee.

Input:

type(emre)

Output:

__main__.Employee

You can access class and instance attributes and call a class method by using the class object. To do so, you must write the object name, followed by the dot (.) operator and the name of the attribute or the method that you want to access or call. Look at the following examples.

Input:

emre.status

Output:

'active'

Input:

emre.number_of_employee

Output:

1

Input:

emre.employee_id

Output:

'101'

Input:

emre.give_info()

Output:

Name: Emre Kutluğ
ID: 101

Attributes

You can use the built-in dir() function to see all of the attributes and methods of an object. There are some built-in attributes and methods in Python. The following example shows all of the attributes and methods of the emre object. The examples with double underscores in front are built-in attributes and methods.

Input:

dir(emre)

Output:

['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'employee_id',
 'give_info',
 'name',
 'number_of_employee',
 'status']

There are two types of attributes that are class and instance attributes. The value of class attributes is the same across all objects, but the value of instance attributes can change across objects. Instance attributes are declared inside any method while class attributes are declared outside of any method. The instance attributes are referred by using the self keyword, while class attributes are referred by the class name inside the method. In the previous Employee class, status and number_of_employee are class attributes, name and employee_id are instance attributes. In the following example, we look first at the value of the number_of_employee attribute, and then we create another object from the Employee class.

Input:

emre.number_of_employee

Output:

1

Input:

emma = Employee("102", "Emma Stone")

Input:

emma.give_info()

Output:

Name: Emma Stone
ID: 102

When we look at the value of number_of_employee in the following example, we see 2 in the output because the number_of_employee attribute is a class attribute. Therefore, it’s shared between the objects.

Input:

emma.number_of_employee

Output:

2

Methods

As mentioned previously, you can implement the functions of an object using methods. I used the objects of a class to call the methods so far, but there is another type of method that is a static method that can be called directly using the class name. Static methods can only access class attributes. In the following example, I add a static method to the Employee class.

Input:

class Employee:

    #class attributes
    status = "active"
    number_of_employee = 0

    def __init__(self, employee_id, name):
        self.employee_id = employee_id #instance attribute
        self.name = name #instance attribute
        Employee.number_of_employee += 1

    #class method
    def give_info(self):
        print("Name:",self.name,"\nID:",self.employee_id)

    @staticmethod
    def get_class_objective():
        message = "The objective of this Employee class is to organize employee information with more modular manner"
        print (message)

Input:

Employee.get_class_objective()

Output:

The objective of this `Employee` class is to organize employee information in a more modular manner.

Global versus local variables

The attributes of a class are also referred to as variables. There are two types of variables: local and global. Local variables in a class are variables that can only be accessed inside the method where it is defined. Therefore, you cannot access a message variable outside of the get_class_objective() method. When you try to access it, it gives the AttributeError, as seen in the following example.

Input:

emre.message

Output:


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-15-35255f09e347> in <module>
----> 1 emre.message

AttributeError: 'Employee' object has no attribute 'message'

Global variables are defined outside of any method, and they can be accessed anywhere in the class. Status and number_of_employee are global variables in the previous example, so we can access them as shown in the following example.

Input:

emre.status

Output:

'active'

Encapsulation

There are three main concepts in object-oriented programming: Encapsulation, Inheritance, and Polymorphism. Encapsulation refers to data hiding. In OOP, one class should not have direct access to the data of the other class, or the access should be controlled through class methods. You can use private variables and properties to control access to class data. To define a private variable, you can put two underscores in front of the variable name. For instance, __age is a private variable.

You can create a property for the age attribute that implements this logic, as shown in the following code. A property has three parts. You must define the attribute, which is age in the following example. Next, you must define the property for the attribute using the @property decorator. Finally, you must create a property setter, which is the @age.setter descriptor in the following example. You can say that the age of employees should always between 18 – 99. If a user tries to enter a value for the age attribute that is less than 18 or greater than 99, there’s an error and an object from the Employee class cannot be created. However, if the value is between 18 – 99, an object can be created.

Input:

class Employee:

    #class attributes
    status = "active"
    number_of_employee = 0

    def __init__(self, employee_id, name, age):
        self.employee_id = employee_id #instance attribute
        self.name = name #instance attribute
        self.age = age #instance attribute
        Employee.number_of_employee += 1


    # Creates model property
    @property
    def age(self):
        return self.__age

    # Create property setter
    @age.setter
    def age(self, age):
        if age < 18:
            raise Exception('An Employee\'s age cannot be lower than 18')
        elif age > 99:
            raise Exception('An Employee\'s age cannot be upper than 99')
        else:
            self.__age = age


    #class method
    def give_info(self):
        print("Name:",self.name,"\nID:",self.employee_id)

    @staticmethod
    def get_class_objective():
        message = "The objective of this Employee class is to organize employee information with more modular manner"
        print (message)

Input:

child = Employee("103", "Eric Cartman", 12)

Output:

---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-18-1bdb73cbdda9> in <module>
----> 1 child = Employee("103", "Eric Cartman", 12)

<ipython-input-17-686e6830c0b5> in __init__(self, employee_id, name, age)
      8         self.employee_id = employee_id #instance attribute
      9         self.name = name #instance attribute
---> 10         self.age = age #instance attribute
     11         Employee.number_of_employee += 1
     12

<ipython-input-17-686e6830c0b5> in age(self, age)
     21     def age(self, age):
     22         if age < 18:
---> 23             raise Exception('An Employee\'s age cannot be lower than 18')
     24         elif age > 99:
     25             raise Exception('An Employee\'s age cannot be upper than 99')

Exception: An Employee's age cannot be lower than 18

Inheritance

Inheritance in OOP is similar to real-world inheritance where a child inherits some of the characteristics from his parents, in addition to his own unique characteristics. The class that inherits another class is called a child class, and the class that is inherited by another class is called a parent class. The following code shows an example of inheritance.

Input:

# Create Class Manager that inherits Employee
class Manager(Employee):

    def set_team_size(self, team_size):
        self.team_size = team_size

In the previous example, I create a Manager class that inherits the Employee class. To inherit a class, you must write the parent class name inside the parenthesis that follows the child class name. The Manager class can access all of the attributes and methods of the parent Employee class, like shown in the following example.

Input:

muge = Manager("104", "Müge Özkan", 30)

Input:

muge.name

Output:

'Müge Özkan'

Input:

muge.status

Output:

'active'

Input:

muge.get_class_objective()

Output:

'The objective of this Employee class is to organize employee information with more modular manner'

The Manager class also has its own method set_team_size() in addition to the Employee class’ methods and attributes. You can set the team size of the object of the Manager class as in the following example. As a side note, one class can have more than two parent or child classes.

Input:

muge.set_team_size(10)

Input:

muge.team_size

Output:

10

Polymorphism

Polymorphism refers to the ability of an object to act in different ways. There are two types of polymorphism: method overriding and method overloading.

Method overriding

Method overriding means having a method with the same name in the child class as in the parent class. The definitions of such methods are different in parent and child classes, but the name remains the same. If you remember, we had a give_info() method in the Employee class. We can override this method in the child Manager class to give team size information about manager objects.

Input:

class Manager(Employee):

    team_size = 10

    def set_team_size(self, team_size):
        self.team_size = team_size

    def give_info(self):
        print("Name:",self.name,"\nID:",self.employee_id,"\nTeam Size:",self.team_size)

Input:

muge = Manager("104", "Müge Özkan", 30)

Input:

muge.give_info()

Output:

Name: Müge Özkan
ID: 104
Team Size: 10

Input:

emre.give_info()

Output:

Name: Emre Kutluğ
ID: 101

As you see in the previous example, the give_info() method is being called through both parent and child classes, but they behave differently because the child class has overridden the parent class method.

Method overloading

You can overload any method by changing the number or types of the arguments when you are calling such methods and the methods behave differently. In the following example, if we call the calculate_salary() method with one argument, it returns that argument. However, if we call that method with two arguments, it returns a summation of the two arguments.

Input:

class Manager(Employee):

    team_size = 10

    def set_team_size(self, team_size):
        self.team_size = team_size

    def give_info(self):
        print("Name:",self.name,"\nID:",self.employee_id,"\nTeam Size:",self.team_size)

    def calculate_salary(self, salary, bonus=None):
        if bonus is not None:
            salary += bonus
        return salary

Input:

muge = Manager("104", "Müge Özkan", 30)

Input:

muge.calculate_salary(12345)

Output:

12345

Input:

muge.calculate_salary(12345, 678)

Output:

13023

Conclusion

In this tutorial, I explained object-oriented programming concepts such as classes, objects, attributes, and methods. Then, I continued with Encapsulation, Inheritance, and Polymorphism, which are pillars of OOP. OOP is one of the most commonly used programming paradigms. Therefore, most of the modern programming languages like Python support object-oriented programming.

Emre Kutluğ