# Inheritance¶

A final topic for us to discuss in this introduction to object oriented programming is the concept of inheritance. Working with inheritance provides powerful abstractions and elegant code re-use - it permits a class to inherit and build off of the attributes of another class.

Let’s immediately consider an example of inheritance in action. Let’s revisit the Rectangle class that we wrote in the introduction to this module.

class Rectangle:
""" A class of Python object that describes the properties of a rectangle"""
def __init__(self, width, height, center=(0, 0)):
self.width = width
self.height = height
self.center = center

def __repr__(self):
return "Rectangle(width={w}, height={h}, center={c})".format(h=self.height,
w=self.width,
c=self.center)

def compute_area(self):
return self.width * self.height


Now suppose that we also want to write a Square class, such that only a single side length need be specified to determine its size. Recognize that a square is a special type of rectangle - one whose width and height are equal. In light of this, we ought to leverage the code that we already wrote for Rectangle. We can do this by defining a Square class that is a subclass of Rectangle. This means that Square will inherit all of the attributes of Rectangle, including its methods. Let’s proceed with writing this subclass:

# Creating Square, a subclass of Rectangle
class Square(Rectangle):
def __init__(self, side, center=(0, 0)):
# equivalent to Rectangle.__init__(self, side, side, center)
super().__init__(side, side, center)


Specifying class Square(Rectangle) signals that Square is a subclass of Rectangle and thus it will have inherited the attributes of Rectangle. Next, see that we overwrote the __init__ method that Square inherited; instead of accepting a height and a width, Square should by specified by a single side length. Within this new __init__ method, we pass in that single side length as both the width and height to Rectangle.__init__. super always refers to the “super class” or “parent class” of a given class, thus super is Rectangle here.

Having defined our subclass, we can leverage the other methods of Rectangle as-is. Let’s see Square in action:

# create a square of side-length 2
>>> my_square = Square(2)

# using the inherited get_area method
>>> my_square.get_area()
4

# a square is a rectangle with equal height/width
>>> my_square
Rectangle(width=2, height=2, center=(0.0, 0.0))

>>> my_square.width == my_square.height
True


The built-in issubclass function allows us to verify the relationship between Square and Rectangle.

# Square and Rectangle are distinct classes
>>> Square is not Rectangle
True

# Square is a subclass of Rectangle
>>> issubclass(Square, Rectangle)
True

# my_square is an both an instance of Square and Rectangle
>>> isinstance(my_square, Square)
True

>>> isinstance(my_square, Rectangle)
True


## Summary of Inheritance¶

In general, if you have a class A, then you can define a subclass of A via:

class A:
attr = 0

def method(self):
return 0

# B is a subclass of A
class B(A):
# inherits attr and method
b_attr = -2  # class attribute distinct to B

def method(self):
# overwrites inherited method
return -1


B will have inherited all of the attributes and methods of A. Defining attributes and methods within the definition of B will overwrite those that already exist in A. B is also free to have its own distinct attributes and methods be defined, irrespective of A.

>>> issubclass(B, A)
True

>>> A.attr
0

>>> A().method()
0

>>> B.attr
0

>>> B().method()
-1

>>> B.b_attr
-2
`

We have only scratched the surface of the topic of class inheritance. That being said, this section does convey the essential functionality and utility of class inheritance.