A small review on Object Inheritance in Python (basics)

Over the years, developing software, I sometimes wonder how much do I know about OOP. There's so much components and applications that one builds, that are just functions or a coupĺe of classes from a regular library import. I guess I'll start some review here. Maybe this could serve to anyone, maybe not.

Chairs as Objects

Just like a chair can be assembled while reading the instruction manual, an Object can be "constructed" by a compiler, based on the definitions of a Class. You can think of a chair being an Object and the instruction manual being the Class that orients the individual on assembling it.

$ cat oop.py 
class Chair:

    def __str__(self):
        return "A chair was assembled."

chair = Chair()
print(chair)

$ python3 oop.py
A chair was assembled.


Any object or thing, has its own properties, or characteristics, just like a chair have a color.

$ cat oop.py 
class Chair:

    def __init__(self, color):
        self.color = color

    def __str__(self):
        return f"A {self.color} colored chair was assembled."

chair = Chair(color="black")
print(chair)

$ python3 oop.py
A black colored chair was assembled.


Actions, Features, Properties

Objects can have functionalities or actions, and so Chairs can have, like a lever to lower and raise the seat, or cannot. Therefore, we can assume that a regular chair is different from an office chair, which can rotate or lever the seat. That means, an office chair has a particular set of actions, that a regular chair doesn't have.

$ cat oop.py 
class Chair:

    def __init__(self, color):
        self.color = color

    def __str__(self):
        return f"A {self.color} colored chair was assembled."


class OfficeChair:

    def __init__(self, color):
        self.color = color

    def lever_down(self):
         return "Office chair seat goes down."

    def lever_up(self):
        return "Office chair seat goes up."

    def rotate_left(self):
        return "Office chair rotates to the left."

    def rotate_right(self):
        return "Office chair rotates to the right."

    def __str__(self):
        return f"A {self.color} colored chair was assembled."


chair = Chair(color="black")
print(chair)

office_chair = OfficeChair(color="red")
print(office_chair)
print(office_chair.lever_down())
print(office_chair.rotate_right())

$ python3 oop.py
A black colored chair was assembled.
A red colored chair was assembled.
Office chair seat goes down.
Office chair rotates to the right.


Code Reusability and Organization: The Power of OOP and Inheritance

Although being different types of chairs, both share a common attribute: color.

An office chair is a chair after all, so it's fair to say that an office chair is a derivation of a regular chair: it inherits characteristics and has the same purpose of regular chair.

Therefore, an inheritance can be established from Chair to OfficeChair, eliminating the need of defining a specific color attribute to OfficeChair or even defining a specific __str__ method.

For color attribute, with a little bit of polymorphism, we can redefine the implementation of __init__ method inherited from Chair class, calling __init__() from Chair via super() method, setting the color attribute of Chair, without need of setting specific color for OfficeChair.

Observation: it can also be said that OfficeChair, inherits attributes and methods from Chair.

$ cat oop.py 
class Chair:

    def __init__(self, color):
        self.color = color

    def __str__(self):
        return f"A {self.color} colored chair was assembled."


class OfficeChair(Chair):

    def __init__(self, color):
    	# __init__ was already inherited by Chair and here, it is modified.
        # Instead of setting a class attribute of color for OfficeChair, it just
        # sets the attribute of the upper class (Chair)
        super().__init__(color)

    def lever_down(self):
         return "Office chair seat goes down."

    def lever_up(self):
        return "Office chair seat goes up."

    def rotate_left(self):
        return "Office chair rotates to the left."

    def rotate_right(self):
        return "Office chair rotates to the right."

    # This method is unnecessary, for OfficeChair inherits __str__ method from Chair class.
    #
    # def __str__(self):
    #   return f"A {self.color} colored chair was assembled."


chair = Chair(color="black")
print(chair)

office_chair = OfficeChair(color="red")
print(office_chair)
print(office_chair.lever_down())
print(office_chair.rotate_right())
print("The color of the office chair is: ", office_chair.color)

$ python3 oop.py
A black colored chair was assembled.
A red colored chair was assembled.
Office chair seat goes down.
Office chair rotates to the right.
The color of the office chair is:  red



Grandfather, Father, Son: more inheritance...

Thinking on office chairs, there are ergonomic office chairs, that have a couple more of movements and adjustments. They can lean back or forward, and even have arms that go up and down. But after all, they have also the same functionalities or actions as a regular office chair.

We can now think about three types of chairs:

  • regular chair (Chair)
  • office chair (OfficeChair)
  • ergonomic office chair (ErgonomicOfficeChair)

They are all chairs, so the inheritance easily applies from Chair to ErgonomicOfficeChair, which has its own set of actions like lean back or lean forward, but it also rotates or the seat can go up and down. In other words, an ErgonomicOfficeChair has the same functions as an OfficeChair, and through inheritance, we can use the inherited methods, instead of writting them again.

Also, from ErgonomicOfficeChair, we set the color attribute from Chair and we can use it, for the inheritance of Chair to OfficeChair, is accessible via ErgonomicOfficeChair, which inherits from OfficeChair.

$ cat oop.py 
class Chair:

    def __init__(self, color):
        self.color = color

    def __str__(self):
        return f"[INFO]: A {self.color} colored chair was assembled."


class OfficeChair(Chair):

    def __init__(self, color):
        super().__init__(color)

    def lever_down(self):
         return "Office chair seat goes down."

    def lever_up(self):
        return "Office chair seat goes up."

    def rotate_left(self):
        return "Office chair rotates to the left."

    def rotate_right(self):
        return "Office chair rotates to the right."

    # This method is unnecessary, for OfficeChair inherits __str__ method from Chair class.
    #
    # def __str__(self):
    #   return f"A {self.color} colored chair was assembled."


class ErgonomicOfficeChair(OfficeChair):

    def __init__(self, color):
        super().__init__(color)

    def lean_back(self):
        return "Office chair leans back."

    def lean_forward(self):
        return "Office chair leans forward."



chair = Chair(color="black")
print(chair)

office_chair = OfficeChair(color="red")
print(office_chair)
print(office_chair.lever_down())
print(office_chair.rotate_right())
print("The color of the office chair is: ", office_chair.color)

ergo_office_chair = ErgonomicOfficeChair(color="blue")
print(ergo_office_chair)
print(ergo_office_chair.lean_back())
print(ergo_office_chair.lean_forward())
print(ergo_office_chair.lever_down())

$ python3 oop.py
[INFO]: A black colored chair was assembled.
[INFO]: A red colored chair was assembled.
Office chair seat goes down.
Office chair rotates to the right.
The color of the office chair is:  red
[INFO]: A blue colored chair was assembled.
Office chair leans back.
Office chair leans forward.
Office chair seat goes down.


All this review deserves more writting, for a next article. For now, this pretty much gives an idea of the power of code reusability and organization that OOP provides.

Even though Python is not fully object-oriented due to lack of strong encapsulation, isn't a week language in terms of OOP. That will be something more for another article. See ya.

Mastodon