The Four Pillars of OOP in Python - 01/09/2023
Learn about Abstraction, Polymorphism, Encapsulation and Inheritance in Python
The 4 Pillars of OOP in Python
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)\n
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
.\n\n\n# The parent class\n
\n\n\nclass Amenity:\n def __init__(self, is_running):\n self.is_running = is_running\n def set_is_running(self, currently_running):\n self.is_running = currently_running\n print(f\"{self} is running: {self.is_running}\")\n\n
\n# 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 functionality\n
\nclass Gas(Amenity):\n def __init__(self, is_running, cubic_feet):\n self.cubic_feet = cubic_feet\n super().__init__(is_running)\n
\ndef __str__(self):\n return f\"<{self.__class__.__name__}: {self.cubic_feet}/cuft of gas>\"\n
\nclass Electricity(Amenity):\n def __init__(self, is_running, watts):\n self.watts = watts\n super().__init__(is_running)\n \n def __str__(self):\n return f\"<{self.__class__.__name__}: {self.watts}/watts of electricity>\"\n
\nclass Water(Amenity):\n def __init__(self, is_running, gallons):\n self.gallons = gallons\n super().__init__(is_running)\n
\ndef __str__(self):\n return f\"<{self.__class__.__name__}: {self.gallons}/gallons of water>\"\n
\n# Driver Code\ng = Gas(False, 236)\ne = Electricity(False, 104.2)\nw = Water(True, 16)\n
\n\n\ng.set_is_running(True)\ng.set_is_running(False)\n
\ne.set_is_running(True)\ne.set_is_running(False)\n
\nw.set_is_running(True)\nw.set_is_running(False)\n
\n# Output\n<Gas: 236/cuft of gas> is running: True\n<Gas: 236/cuft of gas> is running: False\n<Electricity: 104.2/watts of electricity> is running: True\n<Electricity: 104.2/watts of electricity> is running: False\n<Water: 16/gallons of water> is running: True\n<Water: 16/gallons of water> is running: False\n
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:
\n# We import ABC and abstractmethod from the abc module\nfrom abc import ABC, abstractmethod\n
\n\n\n# Amenity becomes our abstract base class (ABC)\nclass Amenity(ABC):\n
\n\n\n def turn_on(self):\n pass\n
\n\n\n# We extend our ABC to three new child classes\nclass Electricity(Amenity):\n
\n\n\n def turn_on(self):\n print(\"You flipped the light switch!\")\n
\n\n\nclass Water(Amenity):\n
\n\n\n def turn_on(self):\n print(\"You pressed the faucet up!\")\n
\n\n\nclass Gas(Amenity):\n
\n\n\n def turn_on(self):\n print(\"You turned the knob on the stovefront!\")\n\n
\n\n\n# Driver code\nW = Electricity()\nW.turn_on()\nE = Water()\nE.turn_on()\nG = Gas()\nG.turn_on()\n
\n\n\n# Output\nYou pressed the faucet up!\nYou flipped the light switch!\nYou turned the knob on the stovefront!\n
\n\n
What is happening here?\n\nWe 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.\n\n---\n\n
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.\n
class Gas:\n def __init__(self, price, unit):\n self.price = price\n self.unit = unit\n
def info(self):\n print(f\"I am gas. My price is {self.price}, measured in {self.unit}.\")\n
def make_sound(self):\n print(\"psssssssssssssss\")\n
class Water:\n def __init__(self, price, unit):\n self.price = price\n self.unit = unit\n
def info(self):\n print(f\"I am water. My price is {self.price}, measured in {self.unit}.\")\n
\n\n\ndef make_sound(self):\n print(\"whooooooosh\")\n
\n\n\n# Driver Code\ng = Gas(0.43, 'cu/ft')\nw = Water(0.075, 'gallons')\n
for amenity in (g, w):\n amenity.make_sound()\n amenity.info()\n
# Output\npsssssssssssssss\nI am gas. My price is 0.43, measured in cu/ft.\nwhooooooosh\nI am water. My price is 0.075, measured in gallons.\n
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.\n\nhttps://grandorimichael.medium.com/