Python in Detail: Multiple Inheritance
Welcome to the Multiple Inheritance lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
An earlier lesson showed that classes can inherit methods and attributes from their base classes. A class can also inherit from multiple base classes, which gives it access to all parent classes' methods and attributes.
In the next example,
Catinherits fromVertebrateandPet.>
class Vertebrate:def has_skeleton(self):return Trueclass Pet:def domesticated(self):return Trueclass Cat(Vertebrate, Pet):passms_fluff = Cat()- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
ms_fluff.has_skeleton()Result:
True
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
ms_fluff.domesticated()Result:
True
When we call
ms_fluff.domesticated(), Python has to check multiple classes to find that method. We can think of Python doing the following checks, in the following order, until it finds the method.- Is
.domesticateddefined on the instance itself? - Is
.domesticateddefined onCat(this object's class)? - Is
.domesticateddefined onVertebrate(the first parent class)? - Is
.domesticateddefined onPet(the second parent class)? - If we got this far, give up and raise an
AttributeError.
- Is
In the fourth step, Python finds
Pet.domesticated, so that's what it calls.We call the sequence of checking
Cat, thenVertebrate, thenPetthe "method resolution order", often abbreviated as MRO. The MRO depends on the parent class order. For example, if we define our class asclass Cat(Pet, Vertebrate)instead, then Python checksPetbeforeVertebrate.When multiple parent classes define methods with the same name, the first matching class in the MRO wins. For example, both TVs and computers display things.
>
class TV:def display(self):return "movie"class Computer:def display(self):return "blue screen"class SmartTV(TV, Computer):passsmart_tv = SmartTV()smart_tv.display()Result:
'movie'
In
class SmartTV(TV, Computer),TVcomes first, so it wins.>
class Phone:def display(self):return "call"class Computer:def display(self):return "blue screen"class SmartPhone(Computer, Phone):passphone = SmartPhone()phone.display()Result:
'blue screen'
In
class SmartPhone(Computer, Phone),Computercomes first, so it wins.Let's summarize that. Inheritance order determines method resolution order. Method resolution order determines which methods are actually called at runtime. Calling different methods changes runtime behavior. So changing inheritance order also changes runtime behavior.
We've seen that Python is a dynamic language: we can inspect and change our code's behavior at runtime. The MRO is another example of that: we can directly inspect the method resolution order via
SomeClass.__mro__. This gives us a tuple of classes showing the actual method resolution order that Python uses.>
class Phone:def display(self):return "call"class Computer:def display(self):return "program"class SmartPhone(Phone, Computer):pass[cls.__name__ for cls in SmartPhone.__mro__]Result:
The last class in the MRO is 'object', but we never inherited from that explicitly. It's there because
objectis the parent class of all other classes in Python.We can explicitly inherit from
objectto see that it doesn't affect the MRO. (But note that we never need to do this in real-world code!)>
class Phone(object):passclass Computer(object):passclass SmartPhone(Phone, Computer):pass[cls.__name__ for cls in SmartPhone.__mro__]Result:
['SmartPhone', 'Phone', 'Computer', 'object']
The code above highlights a subtle point: a class shows up only once in the MRO.
PhoneandComputereach haveobjectas a base class, andSmartPhoneinherits from both. However, the MRO only includes theobjectclass once. That's because there's no reason to check for an attribute multiple times.Multiple inheritance enables some design patterns that would be awkward otherwise, like mixins. Mixins are classes that provide methods to other classes via inheritance, but aren't designed to be instantiated directly.
Here are two classes: a regular
Cowclass, and aCirclemixin class, which calculates overlap with a coordinate.>
class Cow:def __init__(self, name):self.name = nameclass Circle:def overlaps(self, other):distance_squared = (self.x - other.x)**2 + (self.y - other.y)**2return distance_squared < (self.radius + other.radius)**2We can tell that
Cowis meant to be instantiated. Why else would it have an.__init__method?The
Circleclass is a mixin. Its.overlapsmethod accessesself.x,self.y, andself.radius, butCircledoesn't define any of those attributes. It expects some other class in the MRO to define them.Now imagine that we're working on a 2D game with cow characters. Collision detection in the game requires us to know whether two cows overlap. We define a class that's both a
Cowand aCircle.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
class CircularCow(Cow, Circle):def __init__(self, name, x, y, radius):super().__init__(name)self.x = xself.y = yself.radius = radius - Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
# Bessie is located at coordinates (0, 0), with a radius of 2.bessie = CircularCow("Bessie", 0, 0, 2)# Daisy is located at coordinates (1, 1), also with a radius of 2.daisy = CircularCow("Daisy", 1, 1, 2)# We've modeled the cows as circles. Each circles's radius is larger than# the distance between the circles, so the circles overlap, occupying the# same space.bessie.overlaps(daisy)Result:
True
We "mixed in" the
.overlapsmethod into ourCowclass, giving us overlap functionality. This might seem a bit strange:Circleis accessing attributes that are only defined in its child class. But Python doesn't care which of the classes defined an attribute. It only cares that the attribute exists on the object.One final note on this example.
CircularCow's.__init__method callssuper().__init__. Only one of our superclasses,Cow, has a.__init__method, sosuper().__init__(...)calls that constructor.What if both superclasses,
CowandCircle, had.__init__methods? How can onesuper().__init__(...)call simultaneously call multiple parent classes'.__init__methods? We'll see the answer a future lesson.Here's a code problem:
The
Adminclass should be aUserwith theIsAdminmixin. Finish theAdminclass by subclassing from both parent classes. Note that the inheritance order matters, since both classes define the same.is_adminmethod!class User:def __init__(self, name):self.name = namedef is_admin(self):return Falseclass IsAdmin:def is_admin(self):return Trueclass Admin(IsAdmin, User):passamir = Admin('Amir')assert amir.name == 'Amir'assert amir.is_admin() == True- Goal:
None
- Yours:
None