Object Oriented Programming (OOP) in Python

13 min read

By Jay Parmar

Python being a general-purpose programming language supports multiple programming paradigms, viz procedural, functional, and object-oriented programming (OOP). Each Pythoneer often uses a combination of these programming styles and usually has her preferred style of coding. As a Python programmer, you can write code in a style that you like.

Considering the number of concepts that OOP encompasses and its popularity, it demands more than one article. However, I will limit the discussion to some of the most widely used object-oriented programming concepts here.

I will cover the following topics and their implementation in Python:

Note: This article assumes some familiarity with Python programming. In case you want to brush up your knowledge on Python, I urge you to go through some of the initial chapters of the Python handbook. Before we jump to the discussion about OOP, let's clear the difference between procedural and functional programming.


Difference between Procedural programming and Functional programming

Procedural programming is the one we learn when we start programming. In its simplest form, procedural programming takes the top-down approach of executing code. The code will be executed line by line sequentially in an order it has been written. That's it, that's procedural programming for you.

If you learn by example, here it is:

Below is the output:

First, this line will execute.
Next, Python executes this line.
Then, this line shows up.
Finally, Python completes execution by printing this line.

Instead of print statements, we can have any code. No matter what code, Python will execute it. In case, the Python interpreter cannot execute the code, it will throw the appropriate error and will finish the execution abnormally. I can say it is a pretty easy programming style.

Next comes the functional programming style. Here, we try to combine code lines into logical blocks that can be reused as and when required.

Say you want to backtest a strategy and write a Python code for it.

The steps to do so usually involves:

  • Downloading the historical data
  • Calculating buy and hold returns
  • Computing the statistical or technical indicators
  • Generating trading signals
  • Calculating strategy returns and other evaluation metrics
  • Visualising the performance of the strategy

Each of the above-listed steps can take one or more lines of code to achieve the defined objective. You can use either approach, procedural or functional, both work. However, the focus, here, would be to understand functional programming. We can create a dedicated function that encapsulates one or more steps defined above.

Below is an example workflow involving various functions to backtest a given strategy:

How many functions should be created and what function performs what functionality generally depends on the coder and how the problem statement is being approached.

Why is a grouping of functionality preferred?

The answer lies in the flexibility it provides. For example, using this programming style, we may choose to create utility functions that can be used across various Python scripts, thereby allowing us to modularise the overall project.

Additionally, it also minimises the chance of accidentally modifying the code that does not require any alteration. As a programmer, we can get a clear idea of which function is causing an error, thereby, focusing only on that particular piece of code.

Consider a scenario that requires a particular task to be executed quite often. If we code it using procedural programming, it involves writing the same piece of code over and over again, and it is not a good programming practice.

Instead, if we use functional programming that defines a function for that particular task, we can call it whenever required without having to repeat the code.

Having this knowledge in mind, we can appreciate how different programming styles enable us as a programmer to code efficiently. Or to say, it allows answering, what programming style is more apt given the scenario.

Onward to the main topic of this article now.


What is OOP and why is it required?

In the virtual world of programming, the OOP enables us to code the real-world objects as they are. The constructs of OOP allow us to define and organise the code such that they reflect the real-world scenarios.

Wondering what I mean by real-world objects? They are cars, books, chairs, keyboards, water bottles, pens, and so on. Intuitively, one can think of these objects to be common nouns. Often these objects are characterised by specific attributes/ properties and functions that they can perform.

Consider a car, for example. It has attributes like colour, transmission type, number of seats, fuel type, and many others. The functions that a car can perform be (self) drive, take a turn, drive reverse, lower windows, apply brakes, turn on/off the engine, play audio, and so on.

The OOP paradigm allows us to write a code that mimics the car's exact behaviour or to say any objects. Hence, the name, object-oriented programming. It enables us to encapsulate the attributes and functions of objects.

This does not mean that other paradigms are not useful; they are, but for different types of applications. Procedural programming might be a preferred choice to create an automation script and not the OOP.

The object-oriented approach enables programmers to write clear and logical code for small and large projects alike with proper organisation.

Some of the popular Python packages that are built using this approach are:

The above list hints that the object-oriented approach enables us to develop large and complex projects with wide-ranging capabilities. At this point, we are sufficiently acquainted with what OOP is and its potential.

Let's learn about some of its primary constructs, classes and objects and see how to implement them using Python.


What are classes and their objects?

Let's continue with the example of a car. If we think in abstract terms, a car is nothing but the replication of an abstract idea. That is, a car itself is a generic term used to define a particular type of vehicle. In other words, it is one of the classes of vehicles. In programming terminology, we represent this abstract idea by a class.

Now, let's think for a minute. If we say that a car is a concept, then what do we call a particular car, such as Toyota Corolla (or any of your favourite ones), what is it? As you might have guessed, it is an object of the car. And what is a car? It is a class (probably under the vehicle universe).

If we take an abstract look, we find that these cars are nothing but the replication of one abstract thing (idea) with different attributes and functions. In programming parlance, this thing is class. In other words, the concept of a car provides us with a template or a blueprint from which we can define/create various objects of the car.

Can you try to think of some other classes and their objects?

Below are some examples:

Class

Object

Mobile

iPhone X

City

Mumbai

Person

Mr Bean

Bike

R 18

At this juncture, I firmly assume that I was able to convey the idea of classes and objects to you. If not, do let me know in a comment below.

It's time to learn to implement these newly learned concepts in Python. The below code shows a class definition in Python.

We define a class with the help of a keyword class, followed by the name of the class, and we end the sentence with :. The body of the class should contain its attributes and functions.

However, we define the class Car with an empty body represented by the keyword pass. In the OOP paradigm, functions that go within the class are referred to as methods; now onwards, I will refer to functions as methods.

Once we have a class defined, we can create its instances, known as objects. The class Car works as a template to create new objects, as shown in the example below:

Often when we create an object of a class, we assign it to some variable. That variable is called the object of the class. Here, in our example, car_1 and car_2 are examples of the class Car. This process is also known as the instantiating of an object. These objects are also known as class instances.

Each of these objects can have different properties, and they can be set as follows:

Now, both objects have the colour attribute. And if we are to print it, we would write as follows:

And the output we will get is the following:

The colour of Car 1 is Carbon Black
The colour of Car 2 is Magma Grey

So far, we have created a class Car and its objects car_1 and car_2. However, currently, the Car class in its current form can hardly be mapped to its real-world counterpart. For example, we know that every car will have certain features in common, like colour, number of types, number of seats, etc., and so some functions. Hence, instead of defining an empty class, we can define a class which encompasses these common attributes and functions.


What are the attributes and methods?

I am pretty sure that you know what I mean by attributes and methods. We have seen examples of attributes and methods multiple times by now. Keeping this in mind, I will directly jump to its implementation in Python.

The below example shows how to define a class with some default attributes and methods:

The updated class definition now resembles a real-world car to some extent. This time it has got two attributes, colour and seating_capacity, and two methods, drive_forward() and lower_windows() built-in it. That means, when we create an object of such a class, it will have these attributes and methods by default.

Of course, we can update these attribute values (neither all cars are White in colour nor all cars have a seating capacity of five). A new car object created below using the new class defined demonstrates this point.

The output is shown below:

The colour of Car 3 is: White
The seating capacity of Car 3 is: 5

As we can see in the above example, we can access a given object's attributes (and methods) using the dot operator. To modify default behaviour, we simply assign new values to attributes as shown below.

It will yield the following result:

The colour of Car 3 is: Magma Red
The seating capacity of Car 3 is: 2

In the real-world analogy, this operation is similar to modifying a car in real. The methods within a class define functionalities of a car. This means we can call methods using the objects only. Because if we don't have a car, there won't be a question of accessing its functionality.

We call methods on car_3, as shown below:

Calling methods, as shown above, would output the following:

Driving 500 meters ahead
Lowering windows on all doors
Windows lowered

One thing to note here is, we cannot alter the behaviour of the methods defined within a class using their objects.

In this fashion, we can create as many objects of the class Car as we need. But wait, let's consider that we create twenty objects.

In this case, the colour attribute of all those objects would have the same value, White. And we all know that in the real world, we have cars with all imaginable colours.

To replicate such a scenario, we might want to change the colour of those twenty objects. In the current implementation, we would need to change the colour attributes of all those objects. This approach does not seem to be efficient.

Instead, what if we can have a facility to change each object's colour the moment we create them? __init__ method to our rescue.


What is init method?

You might have guessed what __init__ does and mean for? If not, here you go, __init__ means initialisation. We use this method to initiate the attributes with values provided by the object when it gets created. In other words, the __init__ methods gets called as soon as a new object is created. Let's implement it in our Car class and see how we can leverage it.

In this implementation of the class, we define all variables (and methods) in the __init__ that needs to be assigned (and called) upon creating a new object. How? As demonstrated in the below code:

We provide the values to be assigned to colour and seating_capacity attributes while creating an object. This way, we can overcome the requirement to set each object's attribute values after they have been created.

If we access the newly created object's attributes, it would have the values we provided while creating them.

The output would be as shown below:

The colour of Car 4 is: Ocean Blue
The seating capacity of Car 4 is: 2

We can also place a method within the body of __init__ to ensure that the method gets executed upon creation of an object. For those of you who come from any other object-oriented programming language, would be able to relate the __init__ method with the constructor method.

You might have noticed that while defining these methods, __init__ or any normal for that purpose, the first parameter these methods take is the self keyword.

Why is this keyword necessary, and why do we need to provide this keyword?
We discuss it next.


What is a self keyword, and why do we use it?

In Python, everything is an object. I mean by this statement that whenever we create any variable in Python, it will be an object of some class, either built-in or user-defined. You may say that it is not the case.

Further, you may say that we can define any variable without the class notation. For example, as shown in the example below, we can define a variable without creating any instance of a class:

x = 'Python is easy.'

That's true. However, when we define a variable in this manner, Python recognises the type of value we assign to the variable and creates the object of the appropriate class on its own. We can check this as follows:

print(type(x))

It will show us that the type of the variable x is str. Does that mean x is an instance of the string class? Let's verify:

print(isinstance(x, str))

Executing the above command returns True as an output, which means that the variable x is an instance/object of the class str.

Now, when I try to, say, count the number of occurrences of t in the variable x using the method count() on x, I need to provide the letter for which I want the number of occurrences. This is shown below:

print(x.count('t'))

Notice that I am not providing the actual string in which the occurrence needs to be counted. Instead, I am invoking the count() method on the object of the string. In this case also, where I am not providing the actual string, I will get the output as 1.

So the question is, how does Python recognise which string to consider?

The answer is, when we call a method using the object, Python passes that object to the calling method and the respective calling method will handle it using the self keyword.

To elaborate, when we call the count() using the notation x.count('t'), Python will send the object x to the count() method. This count() method will then handle the object x using the self keyword. Hence, the self keyword goes as the first parameter in the method definition.

Let's take one more example to make this clear. Recall our Car class. All methods in the Car class have self as the first parameter. Hence, when we call a method as follows:

car_4.lower_windows()

Consider the above command; when we call a method, as shown above, Python will pass the object car_4 to lower_windows() method to convey that you need to perform action mentioned in the method body for the object car_4.

On the receiving side, the method will handle the object using the self keyword. In a nutshell, the self keyword refers to the object that is calling that particular method.

If you try to print the self in the body of the method, it will print the object's memory location. Let's try this out. To do so, I add a new method temp() to the class Car.

Creating a new object of the class Car:

car_5 = Car('Blood Red', 1)

First, I print the newly created object car_5 as

print(car_5)

This outputs the memory location of car_5 on my machine which is,

<__main__.Car object at 0x0000022EB95BEA30>

If I execute the temp() method on car_5 that prints the self keyword, I should get the same output. Here's the try:

car_5.temp()

And the output is

<__main__.Car object at 0x0000022EB95BEA30>

This process validates the claim that the object and the self refers to the same thing.


Summing up

I am hopeful that you have a good idea of classes, attributes, methods and objects. This understanding will allow you to further foray into the world object-oriented programming.

Before concluding, here's my attempt to put everything we covered in this article and quickly summarising it. Below-shown is a new example from the financial markets:

Answer the following questions before reading further:

  1. What is the name of the class?
  2. Which methods will be executed on its own upon creation of new objects?
  3. What parameter do we need to provide while creating an object?
  4. Can we define methods in the above class without the self keyword?
  5. Can we update the value of the short_selling variable while creating the object?
  6. Is it possible to invoke multiple methods upon creation of the object?
  7. Can I say that the default value of short_selling variable will be True for all objects I create?

I hope you won't find these questions difficult. Or have you any doubts or any difficulty answering these questions, do let me know in the comment section below.

Here are the answers:

  1. The class name is Stock.
  2. The __init__ method will get executed on its own every time a new object is created.
  3. We need to pass cn, mp, ex, ts, ss arguments while creating a new object.
  4. Not really. Python will throw an error when we call a method that is defined without the self keyword being its first parameter. This is because Python will automatically pass the calling object to the method, and the method won't be able to handle it.
  5. Nope, we won't be able to update the value of the short_selling variable when creating a new object. We would be able to update its value after the object has been created.
  6. Of course, yes. We can call a method within the body of __init__ method that needs to be called upon object creation.
  7. Yes, the default value of short_selling for all objects will be True.

Creating an object is a straightforward task. It can be created as follows:

AAPL = Stock('Apple Inc.', 234, 'NYSE', 2000, True)

And accessing attributes is also a no-brainer:

print('The total shares of AAPL are', AAPL.total_shares)

It would output the following:

The total shares of AAPL are 2000

Concluding Notes

In this article on object-oriented programming, you learned a few of the building blocks in detail. We started with the difference between procedural and functional programming.

Then you understood what object-oriented programming allows us to mimic the real-world and how it binds everything using the concepts of an object. Along the lines, you'd seen how to define classes and create objects.

You also learned how to make classes with (default) attributes and methods. Towards the end, we understood how the __init__ method helps us and the use of the self keyword.

This article allows us to get started with OOP and by no means is comprehensive coverage on the topic. I plan to cover advanced topics on this subject in the upcoming article. Thanks for reading. Adios.

Disclaimer: All investments and trading in the stock market involve risk. Any decisions to place trades in the financial markets, including trading in stock or options or other financial instruments is a personal decision that should only be made after thorough research, including a personal risk and financial assessment and the engagement of professional assistance to the extent you believe necessary. The trading strategies or related information mentioned in this article is for informational purposes only.

Mega Quant Sale 2024