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
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()
Parent: f Parent: g Child: h
In your mental model, you should imagine the
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()
Parent: f Parent: g Child: h
h method doesn't get "copied" from
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
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
class C: pass obj = C() obj.__str__() # inherited from the class named "object"
'<__main__.C object at 0x7f908edd48d0>'
This is very convenient; otherwise, we wouldn't be able to print
obj because doing so implicitly calls
<__main__.C object at 0x7f908edd48d0>
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
Parent2 have an
f method, after all. Similarly, both
Parent3 have a
Child.__mro__ tuple will tell us Python's preferences in terms of where to find methods.
(__main__.Child, __main__.Parent1, __main__.Parent2, __main__.Parent3, object)
for cls in Child.__mro__: print(cls)
<class '__main__.Child'> <class '__main__.Parent1'> <class '__main__.Parent2'> <class '__main__.Parent3'> <class 'object'>
This means we'll use the
f method from
Parent1 instead of the
f method from
Parent1 is higher priority (earlier in the tuple). Similarly, we'll use
Parent2 instead of
Parent3. This means we can predict what the following will print:
Parent1: f Parent2: 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),
Parent1appears first (most to the left) in the list, so that is the highest priority; similarly,
Parent2is higher priority than
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()
Bottom could inherit
f from either
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
Top, as Bottom => Level_1A => Top is shorter than Bottom => Level_3 => Level_2 => Level_1B => Top. Rule 2 would have also preferred taking
Top, as the Bottom => Level_1A => Top path is follows the leftmost parents up. Yet Rule 3 wins and
f is taken from