
Watch first
Need a bit more background? Or want to dive deeper?
Watch other class-related screencasts.
Transcript:
How does class inheritance work in Python?
Creating a class that inherits from another class
We have a class called FancyCounter
, that inherits from another class, Counter
(which is in the collections
module in the Python standard library):
from collections import Counter
class FancyCounter(Counter):
def commonest(self):
(value1, count1), (value2, count2) = self.most_common(2)
if count1 == count2:
raise ValueError("No unique most common value")
return value1
The way we know we're inheriting from the Counter
class because when we defined FancyCounter
, just after the class name we put parentheses and wrote Counter
inside them.
To create a class that inherits from another class, after the class name you'll put parentheses and then list any classes that your class inherits from.
In a function definition, parentheses after the function name represent arguments that the function accepts. In a class definition the parentheses after the class name instead represent the classes being inherited from.
Usually when practicing class inheritance in Python, we inherit from just one class. You can inherit from multiple classes (that's called multiple inheritance), but it's a little bit rare. We'll only discuss single-class inheritance right now.
Methods are inherited from parent classes
To use our FancyCounter
class, we can call it (just like any other class):
>>> from fancy_counter import FancyCounter
>>> letters = FancyCounter("Hello there!")
Our class will accept a string when we call it because the Counter
class has implemented a __init__
method (an initializer method).
Our class also has a __repr__
method for a nice string representation:
>>> letters
FancyCounter({'e': 3, 'l': 2, 'H': 1, 'o': 1, ' ': 1, 't': 1, 'h': 1, 'r': 1, '!': 1})
It even has a bunch of other functionality too. For example, it has overridden what happens when you use square brackets to assign key-value pairs on class instances:
>>> letters['l'] = -2
>>> letters
FancyCounter({'e': 3, 'H': 1, 'o': 1, ' ': 1, 't': 1, 'h': 1, 'r': 1, '!': 1, 'l': -2})
We can assign key-value pairs because our parent class, Counter
creates dictionary-like objects.
All of that functionality was inherited from the Counter
class.
Adding new functionality while inheriting
So our FancyCounter
class inherited all of the functionality that our Counter
class has but we've also extended it by adding an additional method, commonest
, which will give us the most common item in our class.
When we call the commonest
method, we'll get the letter e
(which occurs three times in the string we originally gave to our FancyCounter
object):
>>> letters.commonest()
'e'
Our commonest
method relies on the most_common
method, which we didn't define but which our parent class, Counter
, did define:
def commonest(self):
(value1, count1), (value2, count2) = self.most_common(2)
if count1 == count2:
raise ValueError("No unique most common value")
return value1
Our FancyCounter
class has a most_commonest
method because our parent class, Counter
defined it for us!
Overriding inherited methods
If we wanted to customize what happens when we assigned to a key-value pair in this class, we could do that by overriding the __setitem__
method.
For example, let's make it so that if we assign a key to a negative value, it instead assigns it to 0
.
Before when we assigned letters['l']
to -2
, we'd like it to be set to 0
instead of -2
(it's -2
here because we haven't customized this yet):
>>> letters['l'] = -2
>>> letters['l']
-2
To customize this behavior we'll make a __setitem__
method that accepts self
, key
, and value
because that's what __setitem__
is given by Python when it's called:
def __setitem__(self, key, value):
value = max(0, value)
The above __setitem__
method basically says: if value
is negative, set it to 0
.
If we stop writing our __setitem__
at this point, it wouldn't be very useful.
In fact that __setitem__
method would do nothing at all: it wouldn't give an error, but it wouldn't actually do anything either!
In order to do something useful we need to call our parent class's __setitem__
method.
We can call our parent class' __setitem__
method by using super
.
def __setitem__(self, key, value):
value = max(0, value)
return super().__setitem__(key, value)
We're calling super().__setitem__(key, value)
, which will call the __setitem__
method on our parent class (Counter
) with key
and our new non-negative value
.
Here's a full implementation of this new version of our FancyCounter
class:
from collections import Counter
class FancyCounter(Counter):
def commonest(self):
(value1, count1), (value2, count2) = self.most_common(2)
if count1 == count2:
raise ValueError("No unique most common value")
return value1
def __setitem__(self, key, value):
value = max(0, value)
return super().__setitem__(key, value)
To use this class we'll call it and pass in a string again:
>>> from fancy_counter import FancyCounter
>>> letters = FancyCounter("Hello there!")
But this time, if we assign a key to a negative value, we'll see that it will be assigned to0
instead:
>>> letters['l'] = -2
>>> letters['l']
0
Summary
If you want to extend another class in Python, taking all of its functionality and adding more functionality to it, you can put some parentheses after your class name and then write the name of the class that you're inheriting from.
If you want to override any of the existing functionality in that class, you'll make a method with the same name as an existing method in your parent class.
Usually (though not always) when overriding an existing method, you'll want to call super
in order to extend the functionality of your parent class rather than completely overriding it.
Using super
allows you to delegate back up to your parent class, so you can essentially wrap around the functionality that it has and tweak it a little bit for your own class's use.
That's the basics of class inheritance in Python.