The 4 Pillars of OOP in Python

manthony
The Startup
Published in
5 min readJan 10, 2021

Abstraction, Encapsulation, Inheritance, Polymorphism are the “four pillars” of Object-Oriented Programming. We will go over a brief explanation of each in Python, and how they relate to the picture of my (wishful) house above.

Encapsulation

My house has a roof to protect the inside from the rain. It has a door to prevent anyone from just wandering in. Sure, it has windows, too- but I have shutters on those and control who I want to be able to peer in. In these ways, my house is encapsulated, or protected, from the outside. When I encapsulate something in my code, I am taking caution to not let outside effects impact that code. When I adhere to this principle, it makes my code more predictable, and not as vulnerable to bugs.

class House:  

def set_wall_material(self, wall_material):
self.wall_matrial = wall_material
def get_wall_material(self):
print(self.wall_material)

What is happening here?

We are using a “setter” and a “getter” to first set the attribute wall_material on our House instance, and then get that attribute with a separate method, get_wall_material. Note that we did not initialize our House with a wall_material, instead, we are defining methods that handle the getting and setting of the attribute exclusively.

Inheritance

The house has gas, electricity, and water. All three of these things are amenities, and amenities share some things in common. For example, they can all be considered “on” or “off”, but yet they all have a different unit that they are measured in. Let us see how we can create a parent class, Amenity, and extend its functionality to three children classes, Gas, Electricity, and Water.

# The parent classclass Amenity:
def __init__(self, is_running):
self.is_running = is_running
def set_is_running(self, currently_running):
self.is_running = currently_running
print(f"{self} is running: {self.is_running}")
# Children classes. We first use the __init__ method to add properties to the class, and the super().__init__ method to inherit all of the parents class functionalityclass Gas(Amenity):
def __init__(self, is_running, cubic_feet):
self.cubic_feet = cubic_feet
super().__init__(is_running)
def __str__(self):
return f"<{self.__class__.__name__}: {self.cubic_feet}/cuft of gas>"
class Electricity(Amenity):
def __init__(self, is_running, watts):
self.watts = watts
super().__init__(is_running)

def __str__(self):
return f"<{self.__class__.__name__}: {self.watts}/watts of electricity>"
class Water(Amenity):
def __init__(self, is_running, gallons):
self.gallons = gallons
super().__init__(is_running)
def __str__(self):
return f"<{self.__class__.__name__}: {self.gallons}/gallons of water>"
# Driver Code
g = Gas(False, 236)
e = Electricity(False, 104.2)
w = Water(True, 16)
g.set_is_running(True)
g.set_is_running(False)
e.set_is_running(True)
e.set_is_running(False)
w.set_is_running(True)
w.set_is_running(False)
# Output
<Gas: 236/cuft of gas> is running: True
<Gas: 236/cuft of gas> is running: False
<Electricity: 104.2/watts of electricity> is running: True
<Electricity: 104.2/watts of electricity> is running: False
<Water: 16/gallons of water> is running: True
<Water: 16/gallons of water> is running: False

Abstraction

My beautiful blue house on a quaint pond has running water, electricity, and gas- but you do not need to know how each of these things works. Instead, you flip a light switch and the lights come on; You turn the stove on and a blue circle of flame gently rolls upward; You push the faucet handle up and a stream of clear water pours out. The house's walls and foundation abstract away the details of how these things work, and you as my guest enjoy them blissfully unaware of long lengths of wire, deeply buried pipes, and large water tanks that provide you with these amenities. Abstraction in Python is the process of hiding the real implementation of an application from the user and emphasizing only how to use the application. Let’s look at examples of abstract classes in Python:

# We import ABC and abstractmethod from the abc module
from
abc import ABC, abstractmethod
# Amenity becomes our abstract base class (ABC)
class Amenity(ABC):
def turn_on(self):
pass
# We extend our ABC to three new child classes
class Electricity(Amenity):
def turn_on(self):
print("You flipped the light switch!")
class Water(Amenity): def turn_on(self):
print("You pressed the faucet up!")
class Gas(Amenity): def turn_on(self):
print("You turned the knob on the stovefront!")
# Driver code
W = Electricity()
W.turn_on()
E = Water()
E.turn_on()
G = Gas()
G.turn_on()
# Output
You pressed the faucet up!
You flipped the light switch!
You turned the knob on the stovefront!

What is happening here?

We know amenities must have a way to be turned on, but we don’t care how it happens. By enforcing the standard that an amenity will have an on or off method and leaving the implementation up to the child class, we are abstracting away the details and just focusing on how to use the amenity: you can turn it on. We leave the implementation of it to each of our amenity child classes.

Polymorphism

The word polymorphism means having many forms. In programming, polymorphism means the same function name is used for different types. When we previously used inheritance, we explicitly linked parent classes to child classes to write reusable code. Polymorphism is achieving the same outcome of shared properties and methods but there is no link between the code; instead, we write the properties and methods to operate in similar and logical ways, allowing us to freely swap classes and methods and being able to expect a similar output.

# Define two classes that have similar implementation and therefore can be mixed and matched in an iteration.class Gas:
def __init__(self, price, unit):
self.price = price
self.unit = unit
def info(self):
print(f"I am gas. My price is {self.price}, measured in {self.unit}.")
def make_sound(self):
print("psssssssssssssss")
class Water:
def __init__(self, price, unit):
self.price = price
self.unit = unit
def info(self):
print(f"I am water. My price is {self.price}, measured in {self.unit}.")
def make_sound(self):
print("whooooooosh")
# Driver Code
g = Gas(0.43, 'cu/ft')
w = Water(0.075, 'gallons')
for amenity in (g, w):
amenity.make_sound()
amenity.info()
# Output
psssssssssssssss
I am gas. My price is 0.43, measured in cu/ft.
whooooooosh
I am water. My price is 0.075, measured in gallons.

Hopefully, this was a good summary of OOP and my dream home that lies somewhere between the great outdoors and the great imagination.

A dog-loving full stack developer and Marine veteran from New Haven, CT that thoroughly enjoys reading and sharing to solidify his own learning.

https://grandorimichael.medium.com/

--

--