Classes & Subclasses in Python
What ,why, when to use.
Along with functions, classes are the bedrock of Python and many other programming languages; sub classing or inheritance allows you to organize your code and reuse functionality but it might not be clear how and when to use them, let’s have a look…
👋👋 Hi there 👋👋 all my content is free for Medium subscribers, if you are already a subscriber I wanted to say thank you ! 🎉 If not and you are considering subscribing, you can use my membership referral link, you will be supporting this and other high quality content, Thank you !⭐️⭐ Subscribe to Medium ! ⭐️⭐️
🙂 This is a somehow intermediate subject, don't worry if you don't get everything at first, especially these 2 first examples which introduce a lot of new concepts and are the bulk of the matter, you might need to re read them a couple of times or better yet, do a few experiments on your own.
A simple class:
Let’s start with a simple or basic class and go from there…
class cube(object):
"""This class makes cubes"""
# __init__ runs when a new cube is made
def __init__(self, name):
self.name = name
# custom method (a function) that
# simply returns the name variable
def query(self):
return('I am a cube, my name is: ' + self.name)# make 2 cubes:
cube1 = cube('BOB')
cube2 = cube('SUE')# Query 2 cubes:
print(cube1.query())
>>>
I am a cube, my name is: BOBprint(cube2.query())
>>>
I am a cube, my name is: SUE ━━━━━━━━━ ? ━━━━━━━━━Nothing out of the ordinary, an __init__ method that runs upon instance creation, add an attribute and a simple method query() that returns that attribute, the common class if you will. Getting a bit ahead of ourselves: Here cube is in itself a subclass of object, which gives you access to object methods, but you could also write it as class cube(): or even class cube: and it would still work.
Conceptually a class is a template out of which instances are created, if you see the above example, we first define what we are going to make and then we tell the program to make 2 cube instances :
Subclass
A subclass ( or derived class ) like the name implies extends the base class; you use the parent class as a template, and you add something else creating a new template, let’s add some color to our cubes with a new colorCube
that inherits from cube
:
import random
# we use random.choice to pick one of a few colors at randomclass cube(object):
def __init__(self, name):
self.name = name
def query(self):
return('I am a cube, my name is: ' + self.name)class colorCube(cube):
# Add color attribute, note we also call the parents init
# method
def __init__(self,name):
self.color = random.choice(['BLUE', 'RED', 'PURPLE'])
super().__init__(name)
# Call the parent's query method and add new behavior
def query(self):
return super().query() + (' my color is: ' + self.color)colorCube1 = colorCube('BOB')
colorCube2 = colorCube('SUE')print(colorCube1.query())
>>>
I am a cube, my name is: BOB my color is: BLUEprint(colorCube2.query())
>>>
I am a cube, my name is: SUE my color is: RED ━━━━━━━━━ ??? ━━━━━━━━━Line by line:After defining our original or PARENT cube class, we make another class which inherits or subclasses by adding it as an argument :
class colorCube(cube): equivalent to:class CHILD(PARENT):So colorCube is the child, cube is the parent. We then want to add a color attribute to the child, which we do in the __init__ method which runs upon creation, problem is this overwrites the parents' __init__ method, so we need to also call the parents' __init__ method and pass it the name argument, else our cube will be nameless.You use super() to call parent's methods but you can also call them explicitly as we'll see later.Class methods are next, we want to add functionality to the parents’ query() method, to do so we add the extra functionality and then call it with super().query().
Conceptually a template that uses another template.
You can also reuse the parent
class and make as many subclasses as you want:
class polkaDotCube(cube):...class stripedCube(cube):...I am not completing these subclasses for brevity but they are basically the same as the colorCube class, the important part is that you understand the relationships, here in graphical form:
Multiple Inheritance
You are not limited to a single parent, you can inherit from multiple parents and add functionality from each one to your subclass:
import randomclass cube(object):
def __init__(self, name):
self.name = name
def query(self):
return('I am a cube, my name is: ' + self.name)# Our second class we are going to inherit from:
class dimensions():
def __init__(self):
self.width = 20
self.height = 20class colorCubeWithDimensions(cube,dimensions):
def __init__(self,name):
self.color = random.choice(['BLUE', 'RED', 'PURPLE’])
cube.__init__(self,name)
dimensions.__init__(self) def query(self):
return super().query() + (' my color is: ' + self.color ) + '\n' + ('My Dimensions are: WIDTH:' + str(self.width) + ' HEIGHT:' + str(self.height))colorCube1 = colorCubeWithDimensions('BOB')
colorCube2 = colorCubeWithDimensions('SUE')print(colorCube1.query())
print ('----------')
print(colorCube2.query())>>>I am a cube, my name is: BOB my color is: RED
My Dimensions are: WIDTH:20 HEIGHT:20
----------
I am a cube, my name is: SUE my color is: PURPLE
My Dimensions are: WIDTH:20 HEIGHT:20 ━━━━━━━━━ ??? ━━━━━━━━━A few new things going on here:- In order to subclass from 2 classes we simply add the class as an argument : class colorCubeWithDimensions(cube,dimensions)- The second interesting thing here is how we call the __init__ methods of both parents, If you are going to subclass from multiple classes, calling the methods explicitly I think is more readable, but you can also use super(): cube.__init__(self,name)
dimensions.__init__(self)So we are using elements from 2 Parent classes and 1 subclass to create instances :
Subclasses/Inheritance in python is very flexible, you can also inherit sequentially from a number of parents :
import randomclass cube(object):
def __init__(self, name):
self.name = nameclass colorCube(cube):
def __init__(self,name):
self.color = random.choice(['BLUE', 'RED', 'PURPLE'])
cube.__init__(self,name)
# you can also use super().__init__(name)class stripedColorCube(colorCube):
def query(self):
return('I am a cube, my name is: ' + self.name + '\nMy Color is: ' + self.color + ' and I am also striped' )colorCube1 = stripedColorCube('BOB')
colorCube2 = stripedColorCube('SUE')print(colorCube1.query())
print ('----------')
print(colorCube2.query())>>>I am a cube, my name is: BOB
My Color is: BLUE and I am also striped
----------
I am a cube, my name is: SUE
My Color is: PURPLE and I am also striped ━━━━━━━━━ ? ━━━━━━━━━
This is another way of subclassing, instead of multiple classes all at once, we sequentially or vertical inherit from various upper classes or ancestors, the analogy commonly used is grandparent, parent and child or grandchild and the bottom classes are sometimes called derived.This is a simple example where only the __init__ methods are provided, but you can also access parents and grandparents methods at will, the important thing to notice is the new data structure:
Which one of these two to use is really up to you and the data or problem you are trying to recreate, pen and paper is usually a good place to start…
This should cover the basic concepts of subclassing, let’s now look at some pros and cons of using inheritance in your scripts or programs.
Why ?
A lot of code out there uses classes, so a good reason to know about subclasses is so you and I can better read and understand others code, academically though it is usually said that inheritance helps in reusing your code and keeping it DRY
where DRY
stands for Don’t Repeat Yourself
, yet it is very easy to make your code DRY
and also hard to read or inaccessible to all but the most advanced coders, so there are tradeoffs.
The alternative to using subclasses is writing every single class you will need, let’t say you are adding features to some primordial object as we did with our colored/striped/polkadoted cubes, in that case we would need about 3 classes to cover all the cases:
# This example uses NO subclasses or inheritance#CLASSES:class colorCube():
def __init__(self, name):
self.name = name
self.color = 'BLUE'
def query(self):
return('I am a cube, my name is: ' + self.name + ' my color is ' + self.color)class stripedCube():
def __init__(self, name):
self.name = name
self.stripes = True
def query(self):
return('I am a cube, my name is: ' + self.name + ' and I have stripes')class polkaDotCube():
def __init__(self, name):
self.name = name
self.polkadots = True
def query(self):
return('I am a cube, my name is: ' + self.name + ' and I have polkadots')#INSTANCES:cube1 = colorCube('BOB')
cube2 = stripedCube('SUE')
cube3 = polkaDotCube('TIM')#METHODS:print(cube1.query())
print(cube2.query())
print(cube3.query())>>>I am a cube, my name is: BOB my color is BLUE
I am a cube, my name is: SUE and I have stripes
I am a cube, my name is: TIM and I have polkadots ━━━━━━━━━ ? ━━━━━━━━━Beyond the repetition of the self.name and the query method (bolded) , notice that we have suddenly lost the ability to make simple cubes, we will also need to create a new class increasing the repetition if we wanted a new type of cube.These are toy examples for you to learn, but if you have multiple variations of a primordial object, the classes add quickly, let's say you have to change/add/remove one property,attribute or method(s) from all your cubes (like name and query() in our examples), with no inheritance you'd have to change each individual class 6 times vs one single time if you use inheritance:
The trade off it bears repeating is that you will need to sit down and think how your data can be organized in subclasses , figure out the kinks of subclasses (trust me there are a few) and figure out if this is the right approach, for instance if you only wanted to add variation to a few instances of a class, you might be better off using a decorator:
In the end subclasses give you an enormous amount of flexibility into how to model complex things in code and they are good candidates to consider when you encounter complex problems, if you find yourself writing a lot of similar classes that might be a good sign that you need to use inheritance.
Note: I personally don't write a lot of subclasses and inheritance, mostly because I am usually doing disposable prototypes that never get released or refactored, but as I figure out the data relationships sometimes subclasses are needed, you can read more about my thoughts on organizing your code here:Organizing your Python Code
Thanks for reading and I hope this helps you start with subclasses !