Python Fundamentals contents
Inheritance and Operator Overloading
Reuse behaviour with inheritance, override methods, call super(), and teach your classes to work with +, ==, < and len.
Intermediate4 min readoopinheritanceoperatorsdunder
Read first: Classes and Objects
Inheritance
A class can inherit everything from a base class and then add or change behaviour. This models an "is-a" relationship: a Dog is an Animal.
python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def speak(self): # overrides the base method
return f"{self.name} barks"
class Cat(Animal):
def __init__(self, name, indoor=True):
super().__init__(name) # run the base constructor
self.indoor = indoor
animals = [Animal("Generic"), Dog("Rex"), Cat("Tom")]
assert [a.speak() for a in animals] == [
"Generic makes a sound",
"Rex barks",
"Tom makes a sound",
]
assert isinstance(animals[1], Animal) and issubclass(Dog, Animal)super() gives access to the base class, so you extend the parent's behaviour instead of copying it. The loop above is an example of polymorphism: the same call a.speak() does the right thing for each type.
Types of inheritance
- Single:
class B(A). - Multilevel:
class C(B)whereB(A); the chain is followed upward. - Multiple:
class C(A, B). Python searches the parents in the method resolution order (MRO), which you can inspect withC.__mro__.
python
class Swimmer:
def move(self):
return "swim"
class Walker:
def move(self):
return "walk"
class Duck(Swimmer, Walker):
pass
assert Duck().move() == "swim" # Swimmer comes first in the MRO
assert [c.__name__ for c in Duck.__mro__] == ["Duck", "Swimmer", "Walker", "object"]Operator overloading
Special ("dunder", double-underscore) methods let your objects use Python syntax.
| Expression | Method |
|---|---|
a + b |
__add__ |
a - b |
__sub__ |
a * b |
__mul__ |
a == b |
__eq__ |
a < b |
__lt__ |
len(a) |
__len__ |
a[i] |
__getitem__ |
x in a |
__contains__ |
str(a) |
__str__ |
hash(a) |
__hash__ |
A 2D vector that behaves like a number:
python
class Vec:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vec(self.x + other.x, self.y + other.y)
def __mul__(self, k):
return Vec(self.x * k, self.y * k)
def __eq__(self, other):
return isinstance(other, Vec) and (self.x, self.y) == (other.x, other.y)
def __hash__(self): # needed to keep a class hashable once __eq__ is defined
return hash((self.x, self.y))
def __repr__(self):
return f"Vec({self.x}, {self.y})"
a, b = Vec(1, 2), Vec(3, 4)
assert a + b == Vec(4, 6)
assert a * 3 == Vec(3, 6)
assert len({a, Vec(1, 2), b}) == 2 # equal vectors collapse in a setImplement ordering once and let functools.total_ordering derive the rest, or simply return a tuple from __lt__:
python
from functools import total_ordering
@total_ordering
class Version:
def __init__(self, text):
self.parts = tuple(int(p) for p in text.split("."))
def __eq__(self, other):
return self.parts == other.parts
def __lt__(self, other):
return self.parts < other.parts
assert Version("1.10.0") > Version("1.9.9")
assert sorted([Version("2.0"), Version("1.5")])[0].parts == (1, 5)Exercises
- Create
Shapewitharea(), and subclassesCircleandSquare. Print the total area of a mixed list. - Give
Vec__sub__,__neg__and adotmethod. - Make a
Moneyclass that adds amounts of the same currency and raises an error for different currencies.