From Diamonds to Mixins: Demystifying Multiple Inheritance in Python
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 131 | |
Author | ||
License | CC Attribution - NonCommercial - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/69446 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
EuroPython 202488 / 131
1
10
12
13
16
19
22
33
48
51
54
56
70
71
84
92
93
95
99
107
111
117
123
00:00
Case moddingDaylight saving timeInternet service providerRhombusCellular automatonCodeVariable (mathematics)Computer fontObject-oriented analysis and designString (computer science)Inheritance (object-oriented programming)QuicksortObject (grammar)Proxy serverFunction (mathematics)Row (database)Manufacturing execution systemImage resolutionConsistencyOrder of magnitudeCore dumpTime zoneCone penetration testEuler anglesOctahedronDepth of fieldStack (abstract data type)Interface (computing)Client (computing)QR codeCodeGoodness of fitCASE <Informatik>Moment (mathematics)ResultantElectronic mailing listPoint (geometry)Multiplication signIn-System-ProgrammierungExterior algebraRhombusLink (knot theory)Slide ruleAlgorithmRight angleDisk read-and-write headPosition operatorLinearizationBitMixed realityOperator (mathematics)MultiplicationObject (grammar)Process (computing)Communications protocolComputer configurationRevision controlComplex (psychology)Stack (abstract data type)Element (mathematics)Image resolutionCore dumpDigital photographyProgrammer (hardware)Standard deviationLibrary (computing)DiagramOrder (biology)Faculty (division)Universe (mathematics)Sheaf (mathematics)Inheritance (object-oriented programming)Online helpInformationArrow of timeInterface (computing)Formal languageError messageQuicksortMilitary baseClient (computing)Module (mathematics)Functional programmingProxy serverString (computer science)Positional notationKey (cryptography)NumberFunctional (mathematics)Thermal conductivitySubject indexingLengthHierarchyConsistencyCountingMereologyMathematicsBranch (computer science)Attribute grammarSet (mathematics)ReliefLine (geometry)Primitive (album)Extension (kinesiology)Declarative programmingBlogCondition numberNormal (geometry)SequenceLimit (category theory)Computer programmingImplementationParameter (computer programming)Data structureNumbering schemeCompilerSingle-precision floating-point formatEndliche ModelltheorieDialectBasis <Mathematik>Price indexRoutingExistenceDifferent (Kate Ryan album)Type theoryComputer animationLecture/Conference
Transcript: English(auto-generated)
00:05
Good afternoon First of all, here's the QR Code if you're interested in the slides, and we're also going to be looking at some code. So that's available there Or you can use the short URL there
00:22
All this information is going to be available if you want to review it now or afterwards. Okay, so a little bit about me My name is Ariel Ortiz This is a photo of me and some Dutch guy that I met at PyCon US a few years ago. I
00:46
Come from Mexico Mexico is in another continent. So I'm pretty far away from home But I'm really delighted to be here at at Czechia. It's been awesome to to Learn about your culture and to know a little bit about the city
01:02
It's it's been pretty pretty good I'm a full-time faculty member. I'm a professor at the University Tech de Monterrey. It's called Monterrey Tech we're a multi-campus private university the most important one in Mexico, and this is one of my My sections that I was teaching last semester
01:24
So what are what is it that I'm gonna be presenting during this talk? I'm gonna give a brief introduction of what multiple inheritance is I'm gonna talk a little bit about the diamond problem
01:40
How we solve the diamond problem using this thing called the method resolution order We're going to talk about the super function A little bit. We're gonna mention briefly what mixins are how we can use them We're gonna see some alternatives to multiple inheritance. And finally, we're gonna have some conclusions here
02:01
So Let's start talking about multiple inheritance by presenting an example. So let's say that we have a user string This class already exists as part of the Collections package in the Python standard library. It basically is a class which you can
02:21
derive from it you can extend it and It gives you basically all the functionality that you need for a string or something similar that works similar to Primitive string So let's say that you want to create your own class called string counter and you inherit it from that one. Okay, so
02:40
We call this relationship an extension or it extends also known as is okay. This is a UML Diagram of classes and let's say that we also wanted to give it some additional behavior, but this one Provided by another class called counter, which is basically like a dictionary works very similar to a dictionary
03:03
But it keeps the keys of the dictionary are the individual elements contained in in the dictionary well in well in your collection in general and The associated value to those keys are the number of items or the number of times that certain items appear So this is really known in other languages as a multi set
03:23
Okay, it's it's a set that allows you to contain more than than one element. Okay, so you want to have that functionality as well So in this case, it makes sense to actually use multiple inheritance here. So let's start looking at an example Specifically how this would look with
03:43
As Python code so here we have The user string and the counter. Okay, we in Indicate here that we want to inherit from These two these two classes and we can provide additional functionality minimally We would need here to provide at least the way to initialize the two parts of our two different classes
04:06
so here we call the init the dunder init method for the user string and here we call the Dunder init method for the counter. Okay, basically because we're calling it explicitly using the name of the class
04:21
we need to indicate that we're using this or providing the Reference to the self object that has is being initialized at this point. So the way of using it is that we now are able to Instantiate here the string counter class We provide in this case a string
04:41
Okay, so my string counter sc is a string so you can basically use it with string methods So for example here We can see what the value is Let's run it here So our string counter contains a string succeeded Okay, sc in index 0 contains that the character s the length of our string counter is 9
05:07
Okay, and we can call methods like upper and it puts everything in uppercase Okay, but we can also use it as as a dictionary Okay, and we can say well give me the items of our string counter the items are as I mentioned before
05:24
the elements the keys would basically be Each individual element that is in the string and the associated value is how many times it appears Okay, if we request here the the keys, okay, we get exclusively the unique elements okay, and most common basically gives you the same thing as items but in
05:43
Ordered from the greatest number to the smallest one of Occurrences of that those particular items. Okay, so fairly simple to use very convenient so Let's see the situation here. Let's imagine that we have a base class and
06:06
We inherit from that base class another another class here It's called left and also another one called right, but then we decide to Join them together again using a
06:21
New class called derived, okay Derived inherits or extends Left and right which at the same time each individually inherit from base So this is looks like a diamond and this is actually why it's called the diamond problem Okay, some people call it the the diamond problem of death or something like that
06:43
So There are different ways of actually solving this this situation because the problem is for example, if I have a method over here and that method is Inherited both from both paths. Okay, which one do I call or let's say that this method is
07:03
overridden from left and right and I call it from here Which one do I go to I go to this one here or do I go to the original one? Okay, there are many possibilities there. Okay. So first of all, let's um, let's see how C++ actually handles this problem Anybody programs here in C++
07:21
Okay, quite a few. Yes. I'm sorry. Yes. I'm sorry as well. I Have to teach as a professor I have to teach a data structure course and I'm not I'm not entitled to actually choose the language and I have to do I have to use C++ in those courses. So I I think the code of conduct says that we cannot mock other languages. So I apologize for for that. So
07:45
You work with C++. Yeah, it pays the bills. Okay So let's have a look in and see how this would look in C++ just for a comparison and see how Does it in a moment? Okay. So here we have our base class
08:01
It has something called method A method called method. Okay, it just prints method and base And here we have to use something that is called virtual inheritance Okay Which basically tells a compiler that if at some point two different classes join into one using multiple inheritance it only
08:21
Uses a copy of each of the methods and attributes that it has Okay, and at the end for all this to work, here's a derived class You need to indicate that it inherits from left and from right but you need to specify you need to redefine the method here and You tell it what what to do if you want to use the the methods from the superclass you need to call them
08:45
Explicitly here. Okay. They are not called automatically if you don't do it that way The compiler will not allow you to to have your your code work. Okay, so This is how how C++ works in this case So
09:01
In the case of of Python How does Python handle the diamond problem? As I mentioned before some other languages use some other kind of schemes You will find multiple inheritance in not many languages actually the only two mainstream languages that actually support multiple inheritance are Python and C++
09:24
Perl also supports it and it can be can be very arguable at this point if it's still considered mainstream, but well you could say it is but other languages like Like Dylan for example, which is a dialect of LISP also supported There's a language called Eiffel which was probably popular in the 1990s. I think I haven't heard a lot of it about it
09:48
I know that it was used a lot in Europe but I'm not really sure anybody has used Eiffel. No, okay. Yeah, that's what I thought so
10:00
Some languages sort of have some kind of multiple inheritance but with some limitations so Scala has things called traits if you program in Ruby you have modules and you can implement multiple modules or Import multiple models and so on Okay, but as such multiple inheritance, it's it's really supported in in
10:24
Very few languages and the reason is very simple. This this situation of the diamond Problem really can be a little bit complicated Some languages handle things using Alternatives and use multiple interfaces. So if you look at languages that have
10:45
Recently appeared like Go language and like Rust and so on They really limit what you can do and they they can arguably even say that they don't even support some kind of inheritance as we're used to But once again inheritance in practice has shown to be a little bit complicated
11:02
so that this is why more modern languages have suddenly decided not to support it as much as languages that are a few decades old So in the case of Python specifically the way that the diamond problem is sold is something that we call the method of Resolution order or MRO for short, okay
11:25
So the Python MRO It uses an algorithm called the C3 super superclass linearization Algorithm. Okay. This is the algorithm that we use here and It was actually introduced in Python 2.3
11:42
that was that version of Python appeared somewhere in 2003 and All versions Python 2 onwards and of course Python 3 actually supported. Okay, and It's used to determine the method resolution order of any class. Okay
12:00
This algorithm is gonna make sure that All the order in which we look for the methods when we have to execute a certain method All subclasses should come before their base classes. Okay, that makes sense. Okay, so we're in a class hierarchy I always start in the bottom going upwards
12:21
Okay, if we have multiple inheritance, okay The base classes are kept in the same order as specified by the class decoration So I can declare that I inherit from multiple classes that same order is going to be followed okay, once we look for for the methods and
12:40
Finally It is guaranteed that we have a consistent and predictable order. Okay across the inheritance Hierarchy So the algorithm and this this is a little bit mathematical but Let's see if we can explain it more or less quickly
13:01
If you have a class C that inherits from these base classes b1 b2 and and so on. Okay The linearization algorithm in the mathematical Formulation can be specified like this Okay The linearization of an object an object in this case refers to the class that is at the very very top of the hierarchy
13:22
I'm not drawing it over here, but It is implicitly there. Okay. It's just the list containing that that base object Okay, this is the object that everyone descends from okay starting from the new classes supported in Python 2.2 a few a couple of decades ago
13:42
Every class has to Derive from object or another class that derives directly or indirectly from object Okay, and this is the interesting part here. Okay for any other class C Okay, the linearization of C this C over here
14:00
Being C a class that extends b1 b2 and bn. Okay, all those super classes here Okay, this is going to be a list okay composed of a List of with C with the class C and the result of calling this part now that we're gonna refer as merge Where we're gonna obtain the linearization of b1
14:21
The linearization of b2 and so on until we have the linearization of bn Okay, which are all the the base classes and then a list Comprised of b1 b2 and bn, okay, so I know this mathematical notation can be a little bit intimidating Let's have a look at an example so you can see how this works. Okay, so
14:42
When you're working with single inheritance, this works very very easily so well, this would be the base case of our algorithm So the linearization of object is just a list of object. Okay, so this is this part over here The linearization of a this one we just read upwards and following the arrows here in our in our UML
15:03
Diagram, so the linearization of a you start with a class a and then go upwards So we start in a then go to object Then the linearization of B you start here in in B and go upwards Once again, you only have a single inheritance branch over here
15:20
The linearization of C, okay starts here in C and then goes upwards to a an object So that's what we have here But the interesting part here happens with whenever we have multiple inheritance the linearization of D according to the algorithm I just described a moment ago It starts with the current class which is D and then the merge of the linearization of its base classes
15:42
Which are B and C. So it's the linearization of B the linearization of C and then The class is B and C. Okay the list with B and C So here what happens we replace the linearization of B, which we computed over here It's BA object the linearization of C, which is CA object. Okay, which you have over here and
16:04
The list BC is maintained the same. So what we do over here is that For every list that we have here. Each list has something that we call a head which is a very first element Okay, this is the head of the first list the head of the second list the head of the third list and the tail is
16:22
Everything other than the head. Okay. So this is the The tail of this list a an object is the tail of this other list and the tail of this other list is C okay. So what we look here going from left to right we look for the head and We check out and see if it doesn't happen doesn't occur as the tail of any other other lists
16:44
okay, so here's B and In this case B does not happen here in the tail of this one here and it doesn't happen here in the tail of this other list so B is would be the next element that we're gonna add here to the linearization So we would put the B over here and we eliminate it from up here
17:04
So the B is eliminated from here This one remains the same and BC we remove the B here and just keeps the C and we repeat this process here in this case We find the the head of of this list to be a but a is also
17:20
Appearing here in the tail. So if it happens here to be in the head and in the tail we cannot Put it here at this moment yet. Okay, we look for the next head going from left to right So C is the head here. It's not part of the tail of any other of the lists It does happen here, but it's it's also the head. So we take this C and we put it down here
17:44
Okay, and we eliminate the C where it happened Eliminate the C that produces an empty list. So we take it away and now we have the C over here We just need to merge a object and a object here or here. That's pretty trivial because a appears
18:01
In the head position in both places. So a would go To our resulting list here and now we only have object So at the end we end up with this DBC a and object. Okay So this is in general how it works once again, if if you found this a little bit Complicated to understand and you want to see how it works
18:21
You can always check the the link that I provided in the previous slide which explains the algorithm which with much more detail and much more Example examples and what I'm doing here. Okay, so if we look we quickly check. Oh
18:42
Skip this example over here. But well, this was the same Diamond problem over here Let me just show you what this one does. Remember this was where we have the base Left right and derived and in this case we call method here Using the method bit solution order. We're gonna see that in this case
19:04
It executes the one in the in the left Okay, and that's because that this was declared first. Okay, even though derived doesn't have its own method called method Okay, that one's inherited from here and because of the way it's declared that's the one that it actually gets
19:21
executed now in any moment, of course the simplest way to determine the The method resolution order of any class is using an attribute here called dunder and our oh, okay So this one will print us here the order Okay, so it shows that For derived the MRO would be first
19:43
Going derived and left and right and then the base, okay So this explains why we we actually get to see this this method being Upgrading the corresponding result. Okay. So now in this example over here This is one that we just saw a moment ago. So we obtain here DBC a an object
20:02
Okay, now this is the same class that we have there the same hierarchy And if we run it, we got DBC and a an object. Okay, this is exactly what we Obtain over here. Okay The next example that we have here actually It's all very similar, but we get to a point if you look here at the at the diagram
20:23
It's basically the same as the previous example except that I'm saying that D I want to inherit from a why would you want to do that? I'm not really sure but I Mean if you try to do something like this, okay, you introduce a situation which makes this unsolvable, okay When you compute the the merge of
20:42
This part of D you enter into a situation in this case as you can see Where a appears in the head here and in the tail So you can't take a from there and then the other one would be C But C appears in the head and in the tail as well. So there's no possible Solution there. So this actually produces an inconsistency
21:03
Okay, and you literally literally get this errors that says cannot create a consistent method resolution order for basis a and C Okay, and if you look at this example, that's exactly what what happens here Okay
21:21
Okay, so this is the error message cannot create a consistent method resolution Okay So I mean the easiest way to see how the methods are looked for are searched for okay It's just using this and if you don't get any errors before it means is everything's okay And you can always just verify what the exact order is gonna be. Okay
21:42
So let's talk about the super function There is this function called super okay It's a built-in function that returns a proxy object that delegates method calls to a parent or to a sibling Okay, there is a common misconception that super always calls a super classic not necessarily
22:00
Okay, the name probably doesn't help a lot to imagine what really happens it really calls the next The the method that is next in line according to the method resolution order, okay So it could be a sibling class doesn't necessarily to be a parent class. Okay There's this really great
22:20
blog post called Python super considered super You can check it out and see really many many more details on how you can use super Okay, so let's see an example here Imagine that you have something like this you have F that inherits from D and E which at the same time you heard from B and C and so on Okay, so if we look here at our code
22:46
The the method is doing something very simple it's just adding the the name of the class as a string and then calling the Super dot method to to call whatever it is could be a super class or it could be a sibling class Okay, according to the method resolution order. Okay and F
23:04
In its simplest way just calls the super of its methods. Okay, so if you if you run this Okay, the first thing that you see is the the method resolution order So for this particular class hierarchy, it goes it visits first F D E B C A and object lastly and
23:23
Well, the string that is produced is basically the same name of the classes. Okay, but just as a plain String over here. Okay. Now just quickly if you check this. These are alternative ways of Producing this exactly same result. Okay You can call super
23:42
like this Okay, you can specify In this case explicitly super takes two parameters. The first one is the class Where you're gonna start looking for well actually the class starting from there you start in the next one in the method resolution order
24:01
Okay, and you specify this object actually this was a way that you had to do it in Versions previous to Python 2.6 You had to specify these two pieces of data always and they were redundant because I mean F is the name of the class So it was really a little bit inconvenient to have to write it here
24:21
And if you change the name of the class you had to remember to change also the reference that you did over here Okay, so that could get it pretty Pretty cumbersome. Okay, but it's still supported and as you're gonna see later later on You can put actually a different class if you want to okay here we have
24:42
F and here we're calling explicitly D method if you remember D is the next class after it for the first one in the method resolution order. Okay, and This basically means that it's gonna start looking from here so at the end it produces exactly the same result
25:05
Okay. Now the interesting things here happen if you do something like this, okay, if you say F plus a method I'm saying basically start calling the the method not immediately from its next ancestor or
25:21
sibling but go directly to starting here from From a okay. So if we run this over here Okay, you can see that it only produces if I the method resolutions the same but I'm saying start in a different in a different Point, okay, so that that could also work in Peculiar ways. Okay. This one gives you exactly the same result
25:43
Now one weird thing here is that you can define your own method route resolution order, okay Now just a warning here just because you can doesn't mean you should Okay, so let's have a look here we have this class hierarchy, okay, so
26:05
Basically this is something similar now, we're just adding here a condition just to make sure that You don't call the method that doesn't exist. Okay, and Let me just eliminate this part over here
26:22
Okay So this is a normal resolution CBA and then object, okay So it's totally sequential CBA because it's just using single inheritance but if you add this and this is where it gets Quite interesting here. I'm telling it that I want to use this a meta class my our Oh, which
26:45
Establishes Here the a new resolution order which goes from the current class to a to B and then to object Okay, if I run this, okay, you can see that The resolution order has changed and basically what I specified here Is that I want from C to go directly to a and then to B and then to object
27:04
So I changed the way that this could could be achieved. Okay, once again, why would you want to do that? It could be some use cases, but in general do not try to do this. Okay Unless you really know what you're you're doing Okay, so let's talk a little bit about mixins. Mixins are classes that provide some additional functionality
27:23
so Here's a very simple example you have a JSON mix mixin that just does the dump operation over here You have a person Okay, and an employee class that uses a JSON mixin and the person we only add here one attribute which is a position of
27:48
this employee and Well, this is a very simple example. The the class JSON mixins is very simple It's not meant to be used by its own. We just added to add some additional functionality and well here We're cleaning an employee. This is that
28:02
For the guy the name of the the guy who was in the photograph a moment ago this Dutch programmer here and You just need to call to JSON you inherited that from the from the mixin. Okay well
28:20
Pitfalls of multiple inheritance you have the diamond problem. You can have naming conflicts increase complexity unexpected behavior So there's pretty nasty things. So what alternatives do we have? We can here We recommend here to favor composition over inheritance and apply the ISP
28:40
Okay, so favoring composition. It is often more flexible and convenient to build classes by combining behaviors. Okay So I'm actually providing here an example We don't have time to see it because we're out of time now But let me just explain briefly if you want to look at the code I provided here the code for these two classes one
29:01
Uses inheritance and the other one uses composition in general if you can this one is considered superior specifically because this one here Gives you a lot of functionality that not necessarily is Appliable to a stack so stacks you typically use them to push and pop elements from there but because I inherit from user list I can use all the
29:21
operations that are Defined for a list also as part as a stack so I can insert elements anywhere for example and of course I would be violating the The definition of what a stack is but if you use composition, you only provide the methods that you require and You use a user list object and through composition
29:43
And you only guarantee that it is used exactly as you want. Okay the interface aggregation principle Says that you should use only or provide Very specific interfaces and your clients only implement those that make sense so I provided a few examples here of using
30:06
Protocols okay if you use type hints and so on protocols is probably the best option that you have in modern Python okay, so you check the examples that we have there and Just to end up here If you're using multiple inheritance use it with caution in preference you should
30:24
Provide or prefer to use single inheritance or even not inheritance at all. Okay, you should probably prefer to use composition If you are defining your designing a class that uses multiple inheritance just provide a clear documentation and
30:41
Favor always smaller mixins. Okay, so Basically we saw here what multiple inheritance was or is The diamond problem we saw a little bit about the use of super the use of mixins It's rope drawbacks and alternatives using composition and the ISP that interface aggregation
31:04
Principle, okay, so I hope this was interesting for you For me, that's all. Thank you very much