Inheritance with Python

by Jarrett Retz January 23rd, 2021
python programming object orient programming oop inheritance base class derived class

Introduction

Inheritance is a core principle in object-oriented programming. It allows us to reuse functionality or properties, across multiple classes. Therefore, it also reinforces the adage "don't repeat yourself". In this article, we take a quick look at inheritance with Python.

Basic Terminology

Python modules will often have one or more base classes. These are the parent classes that derived classes inherit from. I was reading the Python docs and thinking, what is a derived class?

On W3 schools, I read that it's just another name for the child class. A child class inherits from a parent class.

The relationship is as follows: base class is to derived class, as parent class is to child class.

Now—I hope—we can continue without any confusion.

Inheritance Example

Share Methods

Recently, I have been working on an API client. The API has a series of datasets to access. The datasets have different:

  • parameters
  • parameter values
  • descriptions
  • data

It would be wasteful to develop the same methods for each dataset because all datasets use the same three functions.

  1. GetParameterList
  2. GetParameterValues
  3. GetParameterValuesFiltered

At the same time, they can't all use one class because each dataset could have additional properties or methods. This is unfolding into an opportunity to use inheritance.

We can share the functionality across dataset classes while respecting the differences of each dataset.

Base Class

The base class will have the three shared methods and a couple of properties that apply to each table.

Let's define the base class:

## inheritance_example.py

class BaseClass:
    """Inheritance example class"""
    baseUrl = "https://api.bea.com/v2"

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

    def getParameterList(self):
        # In the real world this would send an API request
        # based on the value of the table
        # i.e self.baseUrl + '/' + self.tableName
        if self.tableName == "NIPA":
            print(self.baseUrl + '/' + self.tableName + "/parameters")
            
            return ['Year', 'Industry', 'Profession']
        elif self.tableName == "GDPByIndustry":
            print(self.baseUrl + '/' + self.tableName + "/parameters")
            
            return ['Year', 'Industry', 'Region']
        else:
            return "Table does not exist"

    def getParameterValues(self, parameter):
        print(self.baseUrl + "/" + self.tableName + "/" + parameter)
        
        return ['2017', '2018', '2019']

I apologize for throwing all that code in your face at once, but I promise to explain it.

This base class has one class variable, baseUrl. This variable will remain the same across all instances and classes.

Then, in the __init__ function, we are initializing our table classes with one variable: the table name.

Finally, we have two of our shared methods. This isn't how they would be implemented in real life, but I hope to provide a decent example. Specifically, the if/else block pertains only to this example to show that different table names will return different parameter values.

Each method prints a URL based on the table name and arguments. Then, it returns example data.

Now, we can define child classes.

Child Classes

In the same file, we define two child classes.

## inheritance_example.py

class BaseClass:
  ...

class NIPA(BaseClass):
    """Example child class"""
    description = "This is a specific dataset"
    tableName = "NIPA"

    def __init__(self):
        super().__init__(self.tableName)
    

class GDPByIndustry(BaseClass):
    """Second Example Child Class"""
    description = "This is a different dataset"
    tableName = "GDPByIndustry"

    def __init__(self):
        super().__init__(self.tableName)

Both of these classes inherit the functionality of our base class because we passed them into the class definition.

class NIPA(BaseClass):

Python supports multiple inheritances, so we can pass more than just one class in (we are not going to in this example).

The super() method that we call in each classes' __init__ method accesses the BaseClass and its methods. So, the line super().__init__(self.tableName) is like doing this:

BaseClass("NIPA")

This code works, but it seems like I'm using a class where I could just use an instance.

Is This, Right?

I had to stop myself because something felt wrong. It felt like I was defining classes where I should be defining instances. However, this is what I want to do.

I don't want the user typing in NIPA and GDPByIndustry because they could potentially type them wrong. The only thing I want the user to do is to enter in different parameter values.

This looks strange because we don't have additional methods for the child classes. Although in the future this won't be the case.

## inheritance_example.py

class NIPA(BaseClass):
    """Example child class"""
    description = "This is a specific dataset"
    tableName = "NIPA"

    def __init__(self):
        super().__init__(self.tableName)
        
    def futureMethodOne(self):
        ...
        
    def futureMethodTwo(self):
        ...

At the end of the article, I briefly talk about what the client will look like. This might clear up confusion about whether or not I should be creating instances as opposed to classes.

Using Inherited Methods

Next, let's use our inherited methods! We can expect that each method in BaseClass is available in the child classes. Additionally, we have access to the properties of the base class.

## inheritance_example.py
class BaseClass:
  ...

class NIPA(BaseClass):
  ...
  
class GDPByIndustry(BaseClass):
  ...

nipa = NIPA()

print("Base URL: " + nipa.baseUrl)
print("Table Name: " + nipa.tableName)
print("Description: " + nipa.description)
print("\n")

parameter_list = nipa.getParameterList()
print(parameter_list)
print("\n")

yearValues = nipa.getParameterValues("year")
print(yearValues)

## Base URL: https://api.bea.com/v2
## Table Name: NIPA
## Description: This is a specific dataset


## https://api.bea.com/v2/NIPA/parameters
## ['Year', 'Industry', 'Profession']


## https://api.bea.com/v2/NIPA/year
## ['2017', '2018', '2019']

You could do the same thing with the GDPByIndustryClass and get a different result for the getParameterList method.

(Bonus) Building The Client

With this setup, we could start building the API client.

## inheritance_example.py
...

class Client:
    NIPA = NIPA()
    GDPByIndustry = GDPByIndustry()

    def __init__(self, api_key):
        self.API_KEY = api_key


api_client = Client("my secret key")

parameter_list = api_client.NIPA.getParameterList()
print(parameter_list)

## https://api.bea.com/v2/NIPA/parameters
## ['Year', 'Industry', 'Profession']

This article covered a lot in a short amount of time. Send me a message on the areas where I need to clear things up, or with any suggestions. Thanks for reading and I hope you were able to learn something along the way!


Have a thought about the article?

Send JRTS a message!

We'll use this email to respond to your message.

Contact