Previous: Class Composition

Object-Oriented Programming allows you to define new classes from existing classes. This is called inheritance. Inheritance enables you to define a general class (a superclass) and later extend it to more specialized classes (subclasses).

A subclass inherits accessible data fields and methods from its superclass, but it can also have other data fields and methods.

The following concepts play the key role in understanding the Class Inheritance.

If class B inherits class A, every method from class A can be performed in B with the result from class A, unless overridden occurs.

Superclass is also known as base class, parent class; Subclass is also known derived class, child class.

Implementing Inheritance:

# Superclass GeometricObject and Subclasses Rectangle and Circle
# Inheritance classes
# from GeometricObject import GeometricObject
import math

class GeometricObject:  # This is the superclass
    def __init__(self, color='blue', filled=True):
        self.__color = color
        self.__filled = filled

    def get_color(self):
        return self.__color

    def set_color(self, color):
        self.__color = color

    def is_filled(self):
        return self.__filled

    def set_filled(self, filled):
        self.__filled = filled

    def __str__(self):
        return 'color:' + self.__color + ', filled: ' + str(self.__filled)

class Circle(GeometricObject):  # Subclass Circle
    def __init__(self, radius):
        # Call the __init__ method from the superclass
        super().__init__()  # Without this line, error will occur
        self.__radius = radius

    def get_radius(self):
        return self.__radius

    def set_radius(self, radius):
        self.__radius = radius

    def get_area(self):
        return self.__radius * self.__radius * math.pi

    def get_diameter(self):
        return 2 * self.__radius

    def get_perimeter(self):
        return 2 * self.__radius * math.pi

    def print_circle(self):
        print(self.__str__() + 'radius: ' + str(self.__radius))

class Rectangle(GeometricObject):  # Subclass Rectangle
    def __init__(self, width=1, height=1):
        super().__init__()
        self.__width = width
        self.__height = height

    def get_width(self):
        return self.__width

    def set_width(self, width):
        self.__width = width

    def get_height(self):
        return self.__height

    def set_height(self, height):
        self.__height = height

    def get_area(self):
        return self.__width * self.__height

    def get_perimeter(self):
        return 2 * (self.__width + self.__height)

def main():
    # Input
    r = float(input("(Circle) Enter a Radius: "))
    w, h = map(float, input("(Rectangle) Enter Width, Height: ").split(","))
    # Circle
    circle = Circle(r)
    print('\\nA Circle', circle)
    print("The radius is", circle.get_radius())
    print("The area is", circle.get_area())
    print("The diameter is", circle.get_perimeter())
    # Rectangle
    rectangle = Rectangle(w, h)
    print("\\nA rectangle", rectangle)
    print("The area is", rectangle.get_area())
    print("The diameter is", rectangle.get_perimeter())

main()

Output:

(Circle) Enter a Radius: 45
(Rectangle) Enter Width, Height: 35, 60

A Circle color:blue, filled: True
The radius is 45.0
The area is 6361.725123519331
The diameter is 282.7433388230814

A rectangle color:blue, filled: True
The area is 2100.0
The diameter is 190.0
# Superclass DrawingObject, Shape and subclass Rectangle
# Multiple Inheritances

class DrawingObject:
    def __init__(self, objectID):
        self.objectID = objectID

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

class Rectangle(DrawingObject, Shape):
    def __init__(self, objectID, color, width=1, height=1):
        # Since there are multiple superclasses we need to specify them
        # Using super() when there is only one superclass
        DrawingObject.__init__(self, objectID)
        Shape.__init__(self, color)
        self.width = width
        self.height = height

    def __str__(self):
        return f"Rectangle {self.objectID}, color={self.color}, width={self.width}, height={self.height}"

def main():
    rectangle1 = Rectangle(123, 'blue', 4, 3)
    rectangle2 = Rectangle(124, 'red', 9, 5)
    rectangle3 = Rectangle(125, 'orange', 10, 5)
    print(rectangle1)
    print(rectangle2)
    print(rectangle3)

main()

Output:

Rectangle 123, color=blue, width=4, height=3
Rectangle 124, color=red, width=9, height=5
Rectangle 125, color=orange, width=10, height=5

Optional: Private attribute. Inheritance allows a subclass to use the methods and attribute of the superclass. If the attribute in the superclass is declared private, it cannot be accessed directly in the subclass. Thus, there must exist a getter and setter method, or a public method in the superclass to retrieve that attribute.

# Inheritance - Superclass and Subclass
# Use private methods for subclasses

class Rectangle:
    def __init__(self, width=1.0, length=1.0):
        self.__width = width
        self.__length = length

    @property
    def Length(self):
        return self.__length

    @Length.setter
    def Length(self, length):
        self.__length = length

    @property
    def Width(self):
        return self.__width

    @Width.setter
    def Width(self, width):
        self.__width = width

    def getArea(self):
        return self.__width * self.__length

    def getPerimeter(self):
        return 2 * (self.__width + self.__length)

class Square(Rectangle):
    def __init__(self, width=1.0, length=1.0):
        # Inherit width, length to side, side
        super().__init__(width, length)
        if self.Length != self.Width:
            raise ValueError(f"Invalid side of square: {self.Length} != {self.Width}")

    # Overridden getArea(self), getPerimeter(self)
    def getArea(self):
        return self.Length * self.Width

    def getPerimeter(self):
        # Or 2 * (self.Width + self.Length)
        return 4 * self.Length

def main():
    s = Square(12, 12)
    print("Area:", s.getArea())
    print("Perimeter:", s.getPerimeter())

main()