A Primer on Object Oriented Programming with Python.

Article Agenda

This article is focused at getting you up and running pretty quick on the concept of OOPs in Python. More specifically, we will cover the following topics:

This article is going to be a quite a bit lengthy since I will be giving you a proper explanation to each concepts that I am going to discuss. But I hope you will have a lot to take away form this.

There is another interesting follow up topic to OOP in Python, which is called inheritance. This is a whole world in itself and for the sake of not converting this article into a full-fledged book, I decided to talk about it in another article. It deserves a dedicated article on its own.

Introduction

One of the most desirable features of Python is Object Oriented Programming (OOP in short). OOP in Python is pretty neat and very organized. Developers really love the flow while writing Python OOP.

So, with that being said, lets quickly jump right in.

Object Oriented Programming

In object-oriented programming, I realized the power of using classes and objects as a paradigm for writing codes. In fact, OOPs, as it is called, is all about using classes and objects. One thing I noticed while using Python’s OOPs is that, its quite different from other programming languages like C, C++. Python’s OOPs is crisp and straight to the point.

By definition:

Object-oriented programming (OOP) is a method of structuring a program by bundling related properties and behaviors into individual objects

Conceptually, objects are like the components of a system. Think of a program as a factory assembly line of sorts. At each step of the assembly line a system component processes some material, ultimately transforming raw material into a finished product. An object contains data, like the raw or pre-processed materials at each step on an assembly line, and behavior, like the action each assembly line component performs.

Class in Python OOPs

ishaan = ['Ishaan', 125, 23]
loviRaj = ['Lovi Raj', 225, 34]
pooja = ['Pooja Rana', 34, 25]
# nameofProfessor = [‘name’, age, id]

But this mode of representation has a big limitation. You’d need to create separate variables for all the professors. Also, lets suppose that a newly joined professor say, Pratheek has not been assigned an id yet. Now, if you are managing a really big data like this one, you might not even realize that Pratheek has no id yet. So, generally, if you try to retrieve Pratheek’s id, you might end up getting his age as shown below:

shaan = ['Ishaan', 125, 23]
loviRaj = ['Lovi Raj', 225, 34]
pooja = ['Pooja Rana', 34, 25]
pratheek = ['Pratheek', 19] # id has not been given yet

#retrieving ids
pooja[1] # will give you pooja's id
pratheek[1] # will give you pratheek's AGE (but needed his id)

This is some sort of inconsistency. Therefore, we need a better way to handle such real-world data. Here comes the class.

Creating class is easy. It is shown below:

class Myclass:
    pass

Now, to solve our above problem using classes, first of all, we would have to declare a class with a useful name, say Professor. Each class in python has its own attributes. These attributes are either the instance variables or the class variables.

class Professor:
    pass

It’s that simple. You have a class named Professor. Now, we will currently leave class as empty. I will fill this once I finish talking about the instances.

Instance (Or Object) in Python OOPs

Instances are ways through which one can assign or retrieve information from the class or perhaps even modify it. Every instance in class is unique having their own blueprints of the class. So, if I create three instances of Professor class, I would have blueprints of Professor’s class in each of these three instances and all the three instances are independent of each other.

The syntax for instantiation is:

instanceName = className(inheritSomething[if any])

Let’s see this in action. In the following code, I have instantiated the class with an instance called prof. Then, I have given the attributes (name, id, age) of the class by using the instance prof.

class Professor:
    pass
prof = Professor() # instantiated the class with prof instance

# using prof instance to give class attributes
prof.name = "ProfessorName" 
prof.id = 123
prof.age = 45

So, with this, we have successfully registered an object of Professor class. Now, lets take a look at the previous case where we don’t have Pratheek’s id. So, in that case you don’t even have to pass the id attribute. Therefore, when the developer tries to retrieve Pratheek’s id, he would be thrown an error instead of giving him the age of Pratheek which is absolutely good since it did not fetch us the wrong value. The below code demonstrates this scenario:

class Professor:
    pass

#instances
pooja = Professor()
pratheek = Professor()

#assigning attributes
pooja.name = "Pooja Rana"
pooja.id = 34
pooja.age = 25

pratheek.name = "Pratheek"
pratheek.age = 19

#retrieving the ids
pooja.id                      # no error
pratheek.id                   # error

In the previous no-class scenario, we could have got an ambiguous value (mismatch of age and id), which would have gotten us into some serious troubles. Also, by using class, the code got a meaningful structure. When you want to add a new attribute, just use an object and give an appropriate attribute name and you are done! That’s why Oops is really interesting to work with.

Ok, so one thing I noticed in the above way of assigning attributes to instances is that, we would have to use the dot and attribute name every time we would want to give a value. This might get cumbersome if you are having really large number of professors in the university. Also, this is a repetitive task. Notice how ‘.name, .id, .age’ is repeated for all the instances? Well, there is a neat way to circumvent this. It is, by using init() method. But before moving onto init method, lets talk about methods for a moment.

Method in Python OOPs

Just like we make use of functions in Python, we can have functions inside class as well. Any function that you define inside class is no longer called a function, it’s called a method. Every method inside a class must’ve it’s first argument as self. There is a reason to it. Which I will be demonstrating in the upcoming code snippet. We can call the function from outside the class using the instance name. The following code snippet demonstrates the methods concept of Python:

class Professor:
    def displayName(self):
        print(self.name)

#instances
pooja = Professor()
pratheek = Professor()

# assigning attributes
pooja.name = "Pooja"
pratheek.name = "Pratheek"

# calling method
pooja.displayName()       # Pooja
pratheek.displayName()    # Pratheek

Here, notice how the displayName gave different outputs for both Pratheek and Pooja. Well, that’s because as I mentioned earlier in this report that, whenever you create an instance, it gets an independent blueprint of the class. Therefore, the attributes of Pratheek are in no way having control over Pooja’s. Hence, we got two outputs.

Okay, lets talk about that self keyword. Now, by default, whenever you call a method, in the backend, python does this conversation implicitly:

className.methodName(instanceVar)

Therefore, pooja.displayName() is converted implicitly to:

Professor.displayName(pooja)

So, as you can see that automatically, python passes the instance as its first argument. Therefore, to receive this argument, we use the keyword self, although you can use any word of your choice.

Now, let me take you back to the init method. Init method is used to initialize attributes to a given object. So, instead of writing object dot attributename every time, we can pass
those attributes values as the parameters to the class name when we declare an instance.
The following code demonstrates the usage of init method:

class Professor:
    def __init__(self, name, id, age):
        self.name= name
        self.age = age
        self.id = id

    def displayName(self):
        print(self.name)

#instances
pooja = Professor('Pooja', 34, 25)
pratheek = Professor('Pratheek',None, 19)

#calling methods
pooja.displayName()        # Pooja
pratheek.displayName()     # Pratheek

The code looks more organized now. See how I’ve passed the attribute values while I declared the object. Also, id has become a mandatory field now. Since Pratheek doesn’t have an id yet, I assigned None as its id.

In python, we do have other method types apart from these normal methods and they are:
class methods and static methods

Methods vs Class Methods vs Static Methods

class Professor:
    classvar = "this is a class variable" # class variable

    def __init__(self, name, id, age):
        self.name = name      # instance variable
        self.age  = age       # instance variable
        self.id   = id        # instance variable

    def displayName(self):
        print(self.name)

#instances
pooja = Professor('Pooja', 34, 25)
pratheek = Professor('Pratheek',None, 19)

In the above snippet, classvar is common variable to all the instances (if you explicitly assign it or not). Therefore, even though we did not mention about classvar in pooja and pratheek, they both will contain a class variable called classvar which can be accessed and used whichever way you’d like.

Now, lets talk about the classmethods. Generally, class methods are used to modify, edit or even delete the class variables. Just like a normal method, it also takes a first argument by default but only in this case, it takes class name as its first argument. Also, whenever you are declaring a class method, you should enclose it using a @classmethod decorator. This is done to show Python that the method defined here is a class method and should not be mistaken with the normal method. Class methods are usually called using the class name. The following code snippet demonstrates the use of classmethod:

class Professor:
    classvar = "this is a class variable" # class variable

    def __init__(self, name, id, age):
        self.name = name      # instance variable
        self.age  = age       # instance variable
        self.id   = id        # instance variable

    @classmethod
    def dean(cls, name):
        cls.dean = f"{name}"

#instances
pooja = Professor('Pooja', 34, 25)
pratheek = Professor('Pratheek',None, 19)

# calling the class method
Professor.dean('LPU Dean')

print(pooja.dean)       # LPU Dean
print(pratheek.dean)    # LPU Dean

In the above snippet, you can see that although I haven’t declared dean attribute for pooja and pratheek, but still it got reflected later as their attributes. This is one of the most common use case of class methods. They are used to set / modify class variables. You could also use instances to set the class variable, but it would be a vague or unsuitable candidate and also, I can’t really think of a case where an object can decide what class variable should the class hold.

Now, comes the static method. Static method, in general, does not serve other purposes rather than to use them as logical connectors which means that it is used to implement some sort of logic within the class and does not affect the instances in any way.

Static methods do not take any default arguments. You can declare a static method using the @staticmethod decorator. It can be called using the class name or instance name. The following snippet shows its usage:

class Professor:
    classvar = "this is a class variable" 

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

    @classmethod
    def dean(cls, name):
        cls.dean = f"{name}"

    @staticmethod
    def displayPrompt():
        print("The details have been entered")

#instances
pooja = Professor('Pooja', 34, 25)
pooja.displayPrompt()       # calling via instance

pratheek = Professor('Pratheek',None, 19)
Professor.displayPrompt()   # calling via class name

# both prints out the same result.

In the above code, both displayPrompt() will output the same result. Also, notice that in it, we haven’t passed any default arguments.

The End is Not Nigh

We discussed a lot in this article. You are now well equipped with the fundamentals to take on this OOPs fight. In future, I will include some Inheritance articles and even some extras that many don't know about OOPs.

Would You Like to Support Me?

If you want to support me and my contents, then go ahead and consider doing it. I would highly appreciate that:

18