Whether you’re a newbie at programming or have a certain level of expertise, you’ve probably heard about Python. It’s a very popular programming language and for good reasons.
Python is known for its easy-to-understand syntax, as it closely resembles human language. The programming language has a large, active community. Hence, there is a great supply of documentation, tutorials, and forums for beginners. It’s not surprising that most beginners take Python as their first programming language.
Also, Python is quite versatile, compared to other programming languages. It’s used in areas such as:
Web development
Data analysis
Artificial intelligence
Scientific computing
Machine learning and more
Python is an object-oriented programming (OOP) language. The language was built with object-oriented principles in mind and these OOP features allow programmers to build complex applications and systems using clear logical organizations.
Therefore, as a Python programmer, it’s essential to understand OOP concepts as they are deeply connected to how Python handles data and functions under the hood.
This article will help you:
Understand the basic principles of OOP in Python
Learn how to work with classes and objects
Learn how to implement inheritance and other core OOP concepts
Prerequisites
To sail smoothly with this article, you’ll need the following:
Basic Python knowledge
Zeal to learn
Basics of Object-Oriented Programming in Python
Object-oriented programming (OOP) is a programming paradigm that involves building applications using objects that contain data and methods rather than using functions and logic. This feature allows programmers to create modular and reusable code. Programs built using OOP concepts are more manageable and efficient, as there is less redundancy.
Furthermore, OOP in Python helps manage complexity by modeling real-world units as software objects that have some data or operations associated with them. For example, you could create a program that models a car object. This car could have features or attributes like color, model, and mileage. Your car could also perform operations like start, stop, drive, and honk.
Don't worry if you still need to get a good grasp of the concepts. You'll get a clearer picture when we discuss classes and objects.
What are Classes and Objects in Python?
A class is simply a template for creating objects. Python programmers use classes to define data structures by putting together attributes and methods (functions) as a single unit. The methods operate on the data.
Python classes don't contain actual data. Think of it as a blueprint that programmers use to build objects that will contain data.
You can create a class using the 'class' keyword with the class name and a colon. Python identifies whatever you indent below the colon as part of the class. For example, let's create a simple car class below:
# oop.py
class Car:
pass
For now, your Car
class has a single statement, the pass
keyword. Python programmers use this keyword as a placeholder for where code will go later. If you run this simple program on your computer, it'll not return any error. The code looks boring now, but you'll spruce it up shortly with properties a car object should possess.
Now that you have a good idea of Python classes, what are Python objects?
Python Objects
Remember, you learned before now that classes are simply templates, right? Well, objects are instances created from these templates. You can think of classes as the blueprint or floor plan for a house. Objects are the buildings erected using the blueprint, having all the characteristics defined in the plan.
In addition, when you create a class, your computer doesn't allocate any memory space until you create an instance of the class (object).
Now, you'll spruce up the Car class you created above. You'll achieve this by adding the .__init__()
method. It's a special method, and it's also called a “constructor”. Its primary purpose is to declare and initialize each attribute every instance of your class would have.
Your Car
class would have attributes such as color
, model
, and mileage
. These attributes are called instance attributes. However, you can also create attributes outside the .__init__()
method. This kind of attribute comes directly under the class, and it's called a class attribute. You'll learn more about class attributes later.
Example
Update your Car
class and add the color
, model
, and mileage
attributes. Then, create an instance of the class:
# oop.py
class Car:
def __init__(self, color, model, mileage):
self.color = color
self.model = model
self.mileage = mileage
# Creating an instance of Car
car_1 = Car("blue", "XVZ23", 124)
# Accessing attributes
print(car_1.color)
print(car_1.model)
print(car_1.mileage)
Output:
blue
XVZ23
124
In this example:
The
Car
class has a.__init__()
method withself
, and the parameters,color
,model
, andmileage
self
refers to the current instance of the class (car_1
in this case), and this is used to access the class attributes (color
,model
, andmileage
)When creating the instance,
car_1
, the arguments,blue
,XVZ23
, and124
are passed to.__init__()
self.color
,self.model
, andself.mileage
are initialized with these values
What are the Core OOP Principles?
Object-oriented programming is built on 4 core principles, and the Python programming language adheres to them. You’ll learn briefly about these principles in this section and get more details later in the article.
The principles are:
Inheritance: You can create two different classes and have one of them access the attributes and methods of the other. This situation is called inheritance and allows code reuse, among other functionalities. The class that inherits from the other is called the child class, while the one being inherited from is the parent or base class.
Polymorphism: The term refers to a situation where something can exist in more than one form. Concerning OOP, polymorphism allows Python programmers to use a method or function to achieve more than one goal, depending on the specific type of object one is working with.
For instance, think of yourself as a pet shop owner. You have dogs, cats, and birds in this shop. You can ask these animals to make a sound, and of course, each type of animal will sound differently. However, you don’t know how these animals make their sounds; you only give a command: “Make a sound,” and they do it in their way. Polymorphism in programming is similar.
Encapsulation: This principle refers to the process of creating data structures by putting together data (attributes) and operations (methods) performed on these data as one unit (class). The class often restricts access to some of its components from external interactions. Think of it as protecting the integrity of the object by exposing what’s necessary.
Abstraction: This principle means keeping away the complexity of a program and only exposing the necessary parts. For example, your car object represents a complex machine in the real world, but you’re able to simplify it in the software world using OOP. A typical example of abstraction is the fact that you don’t need to understand the technical intricacies of a car engine to drive a car.
How Do You Work with Classes and Objects?
The process of creating a new object from a class is called instantiating a class. You can instantiate a class by typing the name of the class with opening and closing parentheses:
House()
For each time you instantiate a class, a memory address is allocated to hold the object. You’ll understand this better with the simple demonstration below:
# test.py
class House:
pass
print(House())
Output:
<__main__.House object at 0x1043b1430>
The output above indicates that you have created an object, House
, at the memory address: 0x1043b1430
. Note that the memory address will be different on your local machine.
Instantiate the House
class on the Python console or run the test.py
file once more to see what happens:
<__main__.House object at 0x1043b1430>
>>> House()
<__main__.House object at 0x1043b1b20>
This time, the House
instance is in a new memory address. This instance is different from the first one you created. Your computer assigns a new memory address to every new instance of the class you create.
What are Attributes and Methods in Python?
Attributes are the features or properties possessed by an object. On the other hand, methods are functions defined within the scope of a class. Methods can operate or perform actions on the data of an object.
You define attributes using variables and define methods using functions inside the class. Every method has self as its first parameter, which represents an instance of the class.
Now spruce up your Car
class with the methods, drive()
and honk()
, and run the oop.py
file:
# oop.py
class Car:
def __init__(self, color, model, mileage):
self.color = color
self.model = model
self.mileage = mileage
def drive(self):
return "Vroom Vroom!"
def honk(self):
return "Beep Beep!"
# Creating an instance of Car
car_1 = Car("blue", "XVZ23", 124)
# Accessing attributes and calling methods
print(car_1.color)
print(car_1.mileage)
print(car_1.honk())
print(car_1.drive())
Output:
blue
124
Beep Beep!
Vroom Vroom!
This example shows how you can use methods to perform operations on the car object. The two methods return strings when you call them. Python programmers often create methods that return strings that offer useful information about the class instance.
Now, you’ll learn how class attributes differ from instance attributes.
Class Attribute Vs. Instance Attribute
There are times when you need to define attributes outside the .__init__()
method. These kinds of attributes are shared by every instance of the class and are called class attributes.
Create a Dog
class and assign a class attribute:
# dogs.py
class Dog:
# Class attribute
species = "Canis familiaris"
def __init__(self, name):
self.name = name
def bark(self):
return "Wooowoow"
# Creating instances
dog_1 = Dog("Max")
dog_2 = Dog("Leo")
# Accessing class attribute
print(dog_1.species)
print(dog_2.species)
Output:
Canis familiaris
Canis familiaris
Here, species
is a class attribute that every instance of the class has access to. Its definition is at the class level.
You’re now acquainted with class attributes. So, what are instance attributes?
Instance Attributes
These are attributes that are unique to specific instances of a class. Instance attributes are defined within the .__init__()
method and involve the self
keyword. You’re already familiar with this type of attribute.
Create another Dog
class, but this time, only use instance attributes:
# dogs2.py
class Dog:
def __init__(self, name, species):
# Instance attributes
self.name = name
self.species = species
def bark(self):
return "Wooowoow"
# Creating instances
dog_1 = Dog("Max", "Canis familiaris")
dog_2 = Dog("Leo", "Canis lupus")
# Accessing class attribute
print(dog_1.species)
print(dog_2.species)
Output:
Canis familiaris
Canis lupus
Unlike the dogs.py
example, in dogs2.py
, species
is an instance attribute, and it’s unique to each Dog
instance.
How to Implement Inheritance and Polymorphism in Python
You’ve learned quite a bit about inheritance and polymorphism. You know that inheritance in OOP allows a class to have access to the properties and behaviors of an existing class. Also, polymorphism allows different class instances to be treated as instances of a common superclass.
You’ll learn how to implement these principles.
Inheritance
Different types of animals make up the animal kingdom. Although these animals may differ in a lot of ways, they still share some characteristics. Use this simple analogy to implement the inheritance principle.
Create an Animal
class alongside two other classes, Dog
and Cat
. The first class will be a superclass (parent class), while the other two classes will be subclasses (child classes):
# inheritance.py
# A superclass that other classes inherits from
class Animal:
def speak(self):
pass
# A subclass that inherits from Animal
class Dog(Animal):
def speak(self):
return "woof!"
# A subclass that inherits from Animal
class Cat(Animal):
def speak(self):
return "meow!"
In inheritance.py
, the Dog
and Cat
classes inherit the speak()
method from the Animal
superclass. However, each of the subclasses can speak in their specific ways.
Polymorphism
Think of shapes like rectangles and circles. You can calculate the area of each shape, but the formula for calculating the area of both shapes isn’t the same. Rectangles use length x width
, while circles use π × radius²
.
However, by applying the principle of polymorphism, you can calculate the area of the shape using a common method, calculate_area()
. The method acts differently depending on the shape you’re working on. You’ll see this in action below:
# polymorphism.py
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def calculate_area(self):
return self.length * self.width
class Circle:
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
# Using polymorphism
shapes = [Rectangle(5, 4), Circle(6)]
for shape in shapes:
print("Area: ", shape.calculate_area())
Output:
Area: 20
Area: 113.03999999999999
In polymorphism.py
above, the calculate_area()
method acted differently with the Rectangle
and Circle
classes. Although the method has the same name for both classes, its action isn’t the same. When you call it on a rectangle, it uses length x breadth
, and when you call it on a circle, it uses π × radius²
. That’s what polymorphism is about.
How to Implement Encapsulation and Abstraction in Python
Before now, you’ve had a brief introduction to encapsulation and abstraction in Python. You’ll recall that encapsulation means putting attributes and methods together as a single entity. At the same time, abstraction means hiding the details of a class and showing only essential parts of the object.
You’ll see these principles in practice.
Encapsulation
Think of this OOP principle as a treasure chest. It protects valuable items from outside interference, keeping them safe and secure inside.
Have you ever wondered how the bank stores your money securely? You can access your funds via the ATM or online banking, but you can’t go to the bank’s vault to modify your balance. Encapsulation works similarly.
Note that Python programmers can make an attribute inaccessible outside the class by adding a double underscore __
prefix to the name of the attribute. Such attributes are called private attributes.
Here’s a good example of encapsulation and how to make an attribute private:
# encapsulation.py
class BankAccount:
def __init__(self, initial_balance):
self.__balance = initial_balance
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
def balance(self):
return self.__balance
# Instantiating the class and calling a method
my_account = BankAccount(1000)
my_account.deposit(500)
print(my_account.get_balance())
Output:
1500
In encapsulation.py
:
A user can interact with the
BankAccount
class using the methodsdeposit()
,withdraw()
, andget_balance()
The user doesn’t need to understand the intricate details of how financial institutions store and manage funds in the background
However, you can’t directly access the __balance
attribute outside the method. Attempting to access this attribute will throw an AttributeError
.
Instantiate the BankAccount
class once more, using the same initial_balance
as the last example, and, try to access the __balance
attribute directly:
# Attempting to directly access the _balance attribute
my_account = BankAccount(1000)
print(my_account.__balance)
Output:
Traceback (most recent call last):
File "/Users/emmanueloyibo/Desktop/TechWriting/2. Python_OOP/encapsulation.py", line 31, in <module>
print(my_account.__balance)
AttributeError: 'BankAccount' object has no attribute '__balance'
In the example above:
Attempting to access the
__balance
attribute usingmy_account._balance
results in an error__balance
is a private attribute, and you can’t access it directly outside the method
Abstraction
Imagine you want to brew some coffee. You don’t need to know how the machine grinds the coffee or heats the water. You only need to know what button to press to get your cup of coffee. Similarly, in OOP, abstraction hides the complex inner workings of code and only reveals the part users need to interact with.
Now, you’ll model a coffee machine:
# abstraction.py
class CoffeeMachine:
def __init__(self):
self.water_level = 0
self.beans_level = 0
def add_water(self, amount):
self.water_level += amount
def add_beans(self, amount):
self.beans_level += amount
def make_coffee(self):
if self.water_level >= 1 and self.beans_level >= 1:
print("Coffee is brewing...")
self.water_level -= 1
self.beans_level -= 1
print("Enjoy your coffee!")
else:
print("Sorry, not enough water or beans.")
# Creating an instance and calling methods
coffee_machine = CoffeeMachine()
coffee_machine.add_water(1)
coffee_machine.add_beans(1)
coffee_machine.make_coffee()
Output:
Coffee is brewing...
Enjoy your coffee!
In abstraction.py
above:
Users interact with the
CoffeeMachine
class via the methods (add_water()
,add_beans()
, andmake_coffee()
)Users don’t need to know the complex code that makes each of the methods to run when called
Conclusion
In this piece, you’ve explored Python as an object-oriented language. Let’s recap the key takeaways:
Python, as an object-oriented language, allows programmers to organize their code around classes and objects.
The core OOP principles are inheritance, polymorphism, encapsulation, and abstraction.
Inheritance allows classes to access attributes and methods of other classes. Polymorphism allows objects of different classes to be treated as objects of a common class.
Encapsulation protects the internal states of objects, and abstraction hides the intricate details of objects.
As you continue on your learning journey, don’t hesitate to explore more concepts. Also, endeavor to collaborate and share your knowledge with the Python community.
References
You can explore the following resources to deepen your knowledge of OOP in Python further:
Thanks for reading! If you found this article helpful (which I bet you did 😉), got a question or spotted an error/typo... do well to leave your feedback in the comment section.
And if you’re feeling generous (which I hope you are 🙂) or want to encourage me, you can put a smile on my face by getting me a cup (or thousand cups) of coffee below. :)
Also, feel free to connect with me via LinkedIn.