If we're writing a new class that needs similar methods as another class, we could copy/paste those. A better way is inheritance. class Child(Parent):
means that the new class named Child
inherits methods from the previous class named Parent
.
class Parent:
def f(self):
print("Parent: f")
def g(self):
print("Parent: g")
def h(self):
print("Parent: h")
class Child(Parent):
def h(self):
print("Child: h")
youngster = Child()
youngster.f()
youngster.g()
youngster.h()
In your mental model, you should imagine the f
and g
methods being copied to the Child
class, like this:
class Child(Parent):
def f(self):
print("Parent: f")
def g(self):
print("Parent: g")
def h(self):
print("Child: h")
youngster = Child()
youngster.f()
youngster.g()
youngster.h()
The h
method doesn't get "copied" from Parent
because Child
already has a method called that. The technical way to describe this situation is to say that Child
overrides the h
method of the base class Parent
.
Every class we create has a parent. If we don't specify, its parent is a class named object
(yes, it is confusing that a class is named object
, since we create objects from classes; in Python code, object
isn't an object, it's a class).
This means we always inherit some special methods, like __str__
.
class C:
pass
obj = C()
obj.__str__() # inherited from the class named "object"
This is very convenient; otherwise, we wouldn't be able to print obj
because doing so implicitly calls __str__
:
print(obj)
Every class we create has at least one parent (object
at a minimum), but we're allowed to have 2, 3, or more parents. This capability is called multiple inheritance -- it's a capability that many other programming languages lack.
class Parent1:
def f(self):
print("Parent1: f")
class Parent2:
def f(self):
print("Parent2: f")
def g(self):
print("Parent2: g")
class Parent3:
def g(self):
print("Parent3: g")
class Child(Parent1, Parent2, Parent3):
pass
youngster = Child()
This is a confusing situation! For f
, which method do we inherit? Both Parent1
and Parent2
have an f
method, after all. Similarly, both Parent2
and Parent3
have a g
method.
Fortunately, the Child.__mro__
tuple will tell us Python's preferences in terms of where to find methods.
Child.__mro__
for cls in Child.__mro__:
print(cls)
This means we'll use the f
method from Parent1
instead of the f
method from Parent2
, as Parent1
is higher priority (earlier in the tuple). Similarly, we'll use g
from Parent2
instead of Parent3
. This means we can predict what the following will print:
youngster.f()
youngster.g()
__mro__
stands for "Method Resolution Order", and it's a special attribute that every class has. Don't confuse this with the special methods that we've called on our objects. Contrast youngster.__str__()
(parentheses are necessary for method calls, special or not) with Child.__mro__
(just an attribute, so no parentheses; also, it's a class attribute, in contrast to object attributes we more frequently encounter).
Python has some rules for determining the method-resolution order:
class SomeClass(Parent1, Parent2, Parent3)
, Parent1
appears first (most to the left) in the list, so that is the highest priority; similarly, Parent2
is higher priority than Parent3
Situations often arise where the above rules contradict each other. The above rules are from weakest (1) to strongest (3); this allows us to resolve contradictions. Let's see an example:
class Top:
def f(self):
print("Top")
class Level_1A(Top):
pass
class Level_1B(Top):
def f(self):
print("Level_1B")
class Level_2(Level_1B):
pass
class Level_3(Level_2):
pass
class Bottom(Level_1A, Level_3):
pass
b = Bottom()
b.f()
Here, Bottom
could inherit f
from either Top
or Level_1B
. Level_1B
is a descendent of Top
, so the Level_1B
version is chosen, according to rule 3.
This shows the strength of rule 3. Rule 1 would have preferred taking f
from Top
, as Bottom => Level_1A => Top is shorter than Bottom => Level_3 => Level_2 => Level_1B => Top. Rule 2 would have also preferred taking f
from Top
, as the Bottom => Level_1A => Top path is follows the leftmost parents up. Yet Rule 3 wins and f
is taken from Level_1B
.