Inheritance

Inheritance allows you to reuse code by creating a hierarchy of classes. Let's say that you wanted to create two classes, one for baseball teams and one for football teams. There are going to be some attributes that are common between those classes.

To save code, you might want to create a parent class that's for sports teams in general. It might have things that are common between baseball and football teams.

class Sports:
    def __init__(self, name, city):
        self.name = name
        self.city = city
    
    def go_team(self):
        print("""Let's go {} from the great city of {}.""".format(
          self.name,
          self.city))

You can then pass Sports as an argument to new classes for both Baseball and Football and it will inherit its attributes and methods:

class Baseball(Sports):
    def homerun(self):
        print("We hit a homerun!")

class Football(Sports):
    def touchdown(self):
        print("We scored a touchdown!")

Try out using the go_team() method from the Sports class after initializing an object using the Baseball class:

redsox = Baseball("Redsox", "Boston")
redsox.go_team()

Using super()

You can access the parent's class's method directly in the child's class by using super(). We are going to run away from our sports and baseball example to get a better hold of this.

Imagine that you have a parent class for rectangles and child class for squares (since a square is just a special type of rectangle):

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

    def area(self):
        return self.length * self.width
        
class Square:
    def __init__(self, length_sq):
        self.length_sq = length_sq

    def area(self):
        return self.length_sq * self.length_sq

The area() methods are very similar for both of these, and we could simplify this with super().

If only we could just barely change the __init__ so instead of taking length and width, it would pass the length_sq into the constructor of its parent's class.

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

    def area(self):
        return self.length * self.width
        
class Square(Rectangle):
    def __init__(self, length_sq):
        super().__init__(length_sq, length_sq)

That way when initializing the object, you just need to pass one variable into the Square() class.

sq = Square(2)
sq.area()

To be extra clear, here is what happens:

  1. The "2" from Square(2) is passed into the constructor of Square class:
class Square(Rectangle):
    def __init__(self, 2):
        super().__init__(length_sq, length_sq)
  1. It is then passed into the super() method as its arguments:
class Square(Rectangle):
    def __init__(self, length_sq):
        super().__init__(2, 2)
  1. Which then in turn passes the "2" to the constructor of the parent class:
class Rectangle:
    def __init__(self, 2, 2):
        self.length = length
        self.width = width

Multiple Inheritance

What if you want a child class to inherit from two different parent classes? I actually think that this can be pretty challenging, but as it so often does, stackoverlow comes to the rescue.

First, let's create a Television class:

class Television:
    def __init__(self, channel):
        self.channel = channel
    
    def watch_tv(self):
        print("Its on " + self.channel)

A baseball class might want to inherit properties from both Television and Sports. The constructor in our new baseball class will become a bit more complicated.

Without Super

We are going to explicitly call the constructors from our parent classes and then pass in their arguments into them:

class Baseball(Sports, Television):
    def __init__(self, name, city, channel):
        Sports.__init__(self, name, city)  # explicit calls without super
        Television.__init__(self, channel)
    
    def homerun(self):
        print("We hit a homerun!")

Let check and make sure that it worked:

redsox = Baseball("redsox", "boston", "espn")

# from the baseball class
redsox.homerun()
# from the sports parent class
redsox.go_team()
# from the television class
redsox.watch_tv()

With Super

With super, it is a little more complicated. The first super calls the first parent class, and then we have to explicitly tell it to find everything past the first parent class.

class Baseball(Sports, Television):
    def __init__(self, name, city, channel):
        super().__init__(name, city)  # calls the constructor of Sports
        super(Sports, self).__init__(channel) # constructors after Sports
    
    def homerun(self):
        print("We hit a homerun!")