Quick Guide to Object-Oriented Programming With Python

Quick Guide to Object-Oriented Programming With Python

What is OOP (Object-Oriented Programming)?

If you try to look on the internet or in any textbooks, there is no specific definition of OOP. In general, object-oriented programming is considered a combination of 4 pillar concepts. Those concepts are:

  1. Abstraction (To hide something)

  2. Encapsulation (To encapsulate or join many things)

  3. Inheritance (To inherit something)

  4. Polymorphism (To use for many purposes)

I will explain these concepts in detail as the blog progresses. But the main thing to know in OOP is Classes & Objects. So, what are these Classes and Objects?

Class & Object in Python

In simple terms, A class is just a skeleton or structure of an actual thing (object). For example, a sketch/outline of a building is a class, whereas, an actual building is an Object.

Syntax to create a class:

class ClassName:
        <statement-1>
    .
    .
    .
    <statement-N>

Now, if we apply this Class-Object concept to all humans, then we can have Person as a class with many properties and functionality.

class Person:
    age = 30
    height = 180
    weight = 65

    def say_hello(self):
        print('Hello Person')

Here, we have created a Person class with age, height and weight properties and the say_hello() method or functionality. We have created this Person class, now what?

To use this person class we need to create an object from this Person class. To create an object we use the following syntax:

object_name = ClassName()

To create an object of Person class:

person = Person()
print(person)

""" OUTPUT:

<__main__.Person object at 0x000001C47ED317B0>

"""

When we try to print person, we can see that it is an object of the Person class located at 0x000001C47ED317B0 in memory. To use the property and methods of the class, we can use the following syntax:

class Person:
    age = 30
    height = 180
    weight = 65

    def say_hello(self):
        print('Hello Person')

person = Person()
print("AGE:", person.age)
print("HEIGHT:", person.height)
print("WEIGHt:", person.weight)

person.say_hello()

""" OUTPUT:

AGE: 30
HEIGHT: 180
WEIGHt: 65
Hello Person

"""

This Person class looks very static and with fixed functionality then why I was calling it a schema/outline you might ask?

I kept the class Person simple on purpose to show you the basic use of Class and Objects. So if I were to show you the proper yet simple use of class and object then:

class Person:

    def __init__(self, name, age, height, weight):
        self.name  = name
        self.age = age
        self.height = height
        self.weight = weight

    def show_data(self):
        print('Details: ')
        print('AGE:', self.age)
        print('HEIGHT:', self.height, "cm")
        print('WEIGHt:', self.weight, "kg")

person1 = Person('John Doe', 33, 180, 75)
person1.show_data()

print("-"*15)

person2 = Person('Jane Doe', 31, 160, 65)
person2.show_data()

""" OUTPUT:
Details:
NAME: John Doe
AGE: 33
HEIGHT: 180 cm
WEIGHt: 75 kg
---------------
Details:
NAME: Jane Doe
AGE: 31
HEIGHT: 160 cm
WEIGHt: 65 kg
"""

As you can see in the above code example, I was able to pass different values for name, age, height and weight and created 2 objects from the Person class. I can pass the same or different values as per my need and create an N number of objects because they will be stored at different locations anyway.

person1 = Person('John Doe', 33, 180, 75)
print(person1)
# OUTPUT: <__main__.Person object at 0x000001E6B1C93FA0>

person2 = Person('Jane Doe', 31, 160, 65)
print(person2)
# OUTPUT: <__main__.Person object at 0x000001E6B1C93EB0>

person3 = Person('Jane Doe', 31, 160, 65)
print(person3)
# OUTPUT: <__main__.Person object at 0x000001E6B1C93E50>

In the real-life scenario, when we have to create multiple users (signup functionality), we do not write code for every user. We use class and create objects based on the received details as we did with person1 and person2.

Now, you might have noticed the self keyword and __init__() method in the above code. So, what are these?

What is the init() method in Python?

If you know other languages such as Java, C# or JavaScript, you might be familiar with the concept of a constructor. The init() method is a constructor for a class in Python. For non-technical people, consider the constructor a way to tell the compiler/interpreter to initiate an operation or allocate memory to the object that we are planning to create. But it is not necessary to use the constructor in Python. If we do not provide the __init__() method, the interpreter will create on its own.

In general, we use the init() method to initialize some properties and for inheritance purposes. As we saw earlier, this is how we initialize the variables:

def __init__(self, name, age, height, weight):
        self.name  = name
        self.age = age
        self.height = height
        self.weight = weight

Note: We will discuss how we use the __init__() method for inheritance later.

Now, you might ask, but Sahil, what the heck self is?

What is the ‘self’ keyword in Python?

The self keyword represents the instance of a class. (Just for simple understanding, consider instance as an object generated by the interpreter. Objects and instances are not the same but here consider them the same, just for simple understanding.)

Using this self keyword, we can access all attributes/properties and methods across the class. For instance, in the example below, we have used the self keyword to access name, age, height and weight attributes.

class Person:

    def __init__(self, name, age, height, weight):
        self.name  = name
        self.age = age
        self.height = height
        self.weight = weight

    def show_data(self):
        print('Details: ')
        print('NAME:', self.name)
        print('AGE:', self.age)
        print('HEIGHT:', self.height, "cm")
        print('WEIGHt:', self.weight, "kg")

We can also access methods using the self keyword.

class Person:

    def __init__(self, name, age, height, weight):
        self.name  = name
        self.age = age
        self.height = height
        self.weight = weight

    def show_data(self):
        print('Details: ')
        print('NAME:', self.name)
        print('AGE:', self.age)
        print('HEIGHT:', self.height, "cm")
        print('WEIGHt:', self.weight, "kg")

    def self_demo(self):
        self.show_data()

person1 = Person('John Doe', 33, 180, 75)
person1.self_demo()

""" OUTPUT:
Details:
NAME: John Doe
AGE: 33
HEIGHT: 180 cm
WEIGHt: 75 kg
"""

It is not necessary to use the self keyword. You can use any name, but the condition is, that it should be the first argument of a method. (For init() as well.)

class Person:

    def __init__(random_replacement, name):
        random_replacement.name = name
        pass

    def show_data(random_replacement_1):
        print('Just a Random Thing!')
        print('Name:', random_replacement_1.name)

person1 = Person('John Doe')
person1.show_data()

""" OUTPUT:
Just a Random Thing!
Name: John Doe
"""

As you might have guessed, we don't need to use the same name across the class. The one and only rule is,it should be the first argument! But generally, we prefer the self keyword as it has become almost a standard. If we want to pass any arguments, we must pass them after the self keyword. As we did in the __init__() method.

You can also add any attribute at any time using the self keyword.

class Person:

    def __init__(self, name):
        self.name = name

    def add_age(self, age):
        self.age = age

    def show_data(self):
        print('Name:', self.name)
        print('Age:', self.age)

person1 = Person('John Doe')
person1.add_age(31)
person1.show_data()

""" OUTPUT:
Name: John Doe
Age: 31
"""

Here, we have added age to the class attributes. I do not suggest you do that but just for demo purposes that it is possible. Consider the self keyword as the dictionary and all the attributes and methods as the key of that dictionary.

In the above code example, if we call the show_data() method first. It will throw an error, as we do not have any attribute named age at that time.

This process of combining attributes and methods under one umbrella is called encapsulation.

So, how do we extend the usage and functionality of our class? We can do that using Inheritance and Polymorphism.

Inheritance in Python

As the name suggests, inheritance is the capability to inherit something. In this case, we will be inheriting attributes and methods. For example, we created a class named Person which is too generic. By inheriting the generic class, we can create another class that will be more specific to our needs.

We want to create an Employee class that inherits everything or is inherited from the Person class. So, how do we do it?

Inheritance syntax in Python:

# Base Class/Parent Class
class BaseClass:
        <statement-1>
    .
    <statement-N>

# Derived Class/Child Class
class DerivedClass(BaseClass):
        <statement-1>
    .
    <statement-N>

To create the Employee class from the Person class we can write the following code:

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

    def show_basic_data(self):
        print('Details: ')
        print('NAME:', self.name)
        print('AGE:', self.age)
        print('HEIGHT:', self.height, "cm")
        print('WEIGHt:', self.weight, "kg")

class Employee(Person):
    def __init__(self, employeeId, department, salary):
        super().__init__(name, age, height, weight)
        self.employeeId = employeeId
        self.department = department
        self.salary = salary

    def show_data(self):
        self.show_basic_data()
        print('Employee Id:', self.employeeId)
        print('Department:', self.department)
        print('salary: $', self.salary)

employee = Employee('John Doe', 33, 180, 75, 1 ,'IT', 100000)
employee.show_data()

""" OUTPUT:
Details:
NAME: John Doe
AGE: 33
HEIGHT: 180 cm
WEIGHt: 75 kg
Employee Id: 1
Department: IT
salary: $ 100000
"""

You might have noticed super() in Employee class. What is super() you might ask? The super keyword refers to the Parent class. We use the super() method to access attributes and methods of the parent class. And that is what we did in the __init__() method of the Employee class. We passed some data to the parent class.

Once we have called super() in the __init__() method of the child class, we can use all the attributes and methods of the parent class in our child class. That is what we did in the show_data() method of the Employee class. We could use super().show_basic_data() as well.


Conclusion

Finally! We are at the end of this section 😁.

Let me know if you need any help or want to discuss something. Reach out to me on Twitter or LinkedIn. Make sure to leave any thoughts, questions, or concerns in the comments below. I would love to see them.

Want to learn more? Signup for my Newsletter and get the best articles into your inbox.

Till the next time 👋