Why Object-Oriented Programming (OOP)?
Before we understand what OOP is, let’s understand the backstory, and what led us to formulate “OOP”?
Why programmers thought it was essential to have such a thing? What were the pain points in the traditional way of programming?
Challenge #1: Increased Complexity
Traditional programming (or procedural programming) focuses on breaking a problem into steps or procedures executed one after the other.
While this approach was suitable for small-scale projects or experimentation, it eventually became problematic when projects transformed into large, complex systems.
In other words, it became difficult to manage, understand, and maintain the code as it grew in size and complexity.
Challenge #2: Unorganized Code
One major challenge with traditional programming was the lack of structure and organization.
Before OOP, programs were often written in a linear fashion. This made it difficult to manage large amounts of code and understand the relationships between different parts of the program.
As a result...👇
Challenge #3: Difficulty in Debugging and Testing
...traditional programming resulted in code that was arder to maintain and debug. Consequently, it was prone to more bugs and errors.
This resulted in longer development cycles and reduced software quality.
The list of challenges with traditional programming is endless.
To overcome these challenges, some computer scientists envisioned a new way of programming that would allow developers to create more flexible and adaptable software programs.
The thought process behind the new programming paradigm was inspired by real-world entities. They have certain properties and behaviors, and can interact with one another.
Thus, scientists believed that programming could be modeled in a similar fashion.
And this is how OOP was born in the late 70s.
What is Object-Oriented Programming (OOP)?
As the name suggests, Object-Oriented Programming (OOP) is a programming paradigm/technique based on the concepts of “objects.” That is why the name “object-oriented.”
This is in contrast to traditional programming where methods are executed in sequence.
The core idea of OOP is to model real-world objects and their interactions in a program.
Thus, each object is treated as a separate entity with different values for the properties.
For example, you can model a
car object with properties such as
model, and methods such as
How OOP solved these problems?
In response to the challenges posed by traditional programming, OOP was designed as a new programming paradigm.
Its primary aim was to improve code organization, reusability, maintainability, and scalability.
OOP introduced objects and classes (discussed below), which made it easier to organize code into manageable and reusable components.
Being organized, it was easier to understand the structure of a program, shorten the development lifecycle, and eliminate bugs and errors.
In addition to this, OOP also introduced some crucial concepts, such as inheritance. This provided a way to create relationships between classes.
Moreover, OOP allowed programmers to easily extend existing components and build complex systems. This drastically reduced the amount of code needed to be written again from scratch.
Next, let’s discuss some basic terminologies around classes and how they are defined in Python.
OOP is defined around some core terminologies and concepts. Let’s understand them below:
Class is a blueprint (or template) for instantiating objects. Recall the
Car example discussed above. In that context, we can call
Car a class.
Objects created from the same class will have the same properties and behaviors. However, the values for the properties may differ.
For instance, all car objects created from the
Car class will have the same name of the properties, such as
model etc. However, the values for these properties may differ, as shown below:
In Python, we define a class using the
class keyword, followed by its name:
Inside a class, we define the attributes and methods that its object will have.
The variables defined within a class that store information about an object are called attributes. They can also be thought of as an object’s properties.
Attributes are of two types:
2.1) Instance-level attributes
As the name suggests, these attributes are unique to each instance of a class. In other words, every time we create an object, each object gets its copy of instance-level attributes.
For example, in the
Car example above, the variables
model are instance-level attributes.
Also, these attributes usually have different values for each instance.
In Python, we assign the instance-level attributes in the
__init__ method of a class. As the name suggests, “init” lets us initialize an object.
__init__ is like any other Python function, we can also pass a bunch of parameters to the class method.
The first parameter in a Python class is always the
self keyword. It is a special variable used as a reference to the calling object. The
self keyword is followed by other parameters, as shown below:
The parameters specified in the
__init__ method are assigned to the corresponding instance-level attributes.
2.2) Class-level attributes
In contrast to the instance-level attributes, which are unique to each object, these attributes are shared by all the instances of a class.
In other words, they are associated with the class itself, but not with any specific instance of the class. Therefore, they are defined outside the
For example, you can have a class-level attribute for the
number_of_wheels of a car. This attribute would have the same value for all objects created from the car class, regardless of the
cost, or any other instance-level attributes.
In Python, class-level attributes are defined as follows:
To access the class-level attributes, we can do the following:
The functions defined within the scope of a class are called methods.
They operate on the attributes of an object and are typically used to manipulate or retrieve the values stored in an object’s attributes.
An independent definition created anywhere in the program is called a “function”. However, a function is called a “method” when defined inside a class.
__init__ method, class methods are also defined within the scope of the class. Also, the first parameter is always the
For instance, let’s define a
This method accepts the
new_speed and assigns it to the instance-level attribute
speed. As shown above, we reference any instance-level attribute using the
self keyword using the dot notation.
Note that a class method may or may not receive any parameters other than
self. For instance, if we were to define a
stop_car method, it is not necessary to pass the new speed as a parameter.
Whenever we create an instance of a class, it is called an object.
Car class defined above, we can create a new object as follows:
The above statement invokes the
__init__ method defined in the class. As a result, the arguments get assigned to the respective instance-level attributes defined in the class.
Also, the object can access the attributes and methods defined in the class. These can be accessed using the dot notation, as shown below:
When we call a class method, we don’t pass any value for the
self parameter. For instance, even though the definition of
change_speed() has two parameters:
self parameter is automatically fetched by Python using the calling object.
Therefore, while calling the method, we only passed one value (
20), which corresponds to the
Whenever we define a new class, it creates a new datatype. For instance, if we use the
type() method of Python, and pass the class object
my_car, we get the following:
This indicates that the
my_car object is of type
Thus, in addition to making your code more organized, readable and manageable, classes also offer a mechanism to create a user-defined datatype.
Next, let’s look at some of the fundamental magic methods in Python OOP and how they are used.
In Python, magic methods are methods that have double underscores at the beginning and end of their names, such as
__init__, which we discussed above.