Cooperative Multiple Inheritance in Python: Practice

Introduction

Theoretically, not for any inheritance graph C³ Linearization Algorithm can be linearized. In practice, it is rarely happen, and if it did, Python will emit en Error. Another name of this algorithm is MRO — Method Resolution Order.

That’s right, the Diamond problem can be solved (practically). We can use inheritance where both object has the state, and this will not cause any problem. No more virtual inheritance (as C++ did), or multiple interface inheritance (as Java did).

Note: We will examine below only so called new-style classes. All classes in Python 3 are new style. Everything listed here holds in Python 2 if there no classic style class involve in inheritance graph.

Note: In Python order in which classes appear in inheritance tuple is matter. That’s it, if we have classX(A,B), and in X there is call to super(), Python will check class A first, and then, may be after checking in some other classes (if appropriate method will not be found), it will eventually check in class B.

MRO has following basic properties:

a) Children precede their parents.
b) If a class inherits from multiple classes, they are kept in the order specified in the tuple of the base class.

It also have following properties:

  • Suppose, you have new-style class definition classX(Y1,Y2,,..YN):pass, then MRO of X will start from X [see, proposition (0.0)]
  • object has to be last in (any) MRO consists of new-style objects only. [see, proposition (2.0)]
  • If we have MRO of some node A1, than we chose some node in that MRO, it’s MRO will be subchain of A1 in MRO. [see, proposition (2.1)]
  • Let G be some inheritance graph with at least one node. Let chose some node A1. Let MRO of A1 be L[A1].
    If we define new class B(A1,A2…An), that is class that inherits from n classes, then MRO of B will be L[B]=B+A1+….

[see, proposition (2.2)]

This means that if we have some general form class B(A1,A2…An) the MRO of it will be always B, than first class in the inheritance tuple. In particular if inherit from single class, case n=1, the MRO will starts from B and than (single) parent class A1. More over, when n=2 the MRO will starts from B and then first class in the inheritance tuple A1. There is no guarantee that the next class will be A2, actually it it’s sub-class, see Example 2 below.

  • You can always ask X._mro_ and avoid explicit computing.

Example 1

Based on https://www.python.org/download/releases/2.3/mro/

Suppose, we have following class hierarchy:

classD(O):pass

classE(O):pass

classB(D,E):pass

where O denotes object.

We can visualize the inheritance graph as follows:

Example 1

Based on https://www.python.org/download/releases/2.3/mro/

Suppose, we have following class hierarchy:

classD(O):pass

classE(O):pass

classB(D,E):pass

where O denotes object.

We can visualize the inheritance graph as follows:

Example 1 Inheritance hierarchy

By proposition (0.0) L[B(D,E)]=B…. By proposition (2.0) “the last class is object L[B(D,E)]=…,object=O. So, we know, that

L[B(D,E)]=B,…,O

We assume, that MRO exists, so we have 2 possibilities

1) L[B(D,E)]=B,D,E,O
2) L[B(D,E)]=B,E,D,O

(D, E) appears in tuple inherits of B. By) b) the order that they appears should be preserved, so D should go before E, so option 2) is impossible, that’s why (we assumes that MRO exists and by proposition 1.0 it’s unique) the answer is

L[B(D,E)]=B,D,E,O

Example 2.

Let’s look on another example. Python have standard library class Dict. There is also OrderDict that extends Dict. Python have also Counter class. Counter inherit from Dict. It means, that it counts have many time specific character appear in the string, but the order that it will print out the result is unspecified.

Let’s define custom class

class OrderedCounter(Counter,OrderDict):pass

As you can see in the picture, we have diamond inheritance problem here.

We will not calculate full MRO, but we will put enough constraints on it to establish that first object in the MRO of OrderedCounter is Counter and .

So, we have to prove:

I. MRO of OrderedCounter starts from OrderCounter, Counter.
II. object has to be last in MRO.
III. OrderDict goes before Dict in MRO of OrderedCounter.

I. By proposition (0.0), MRO of OrderedCounter starts from OrderCounter. OrderCounter has 2 direct children Counter and OrderDict. By a) “children precede their parents” The first element in MRO has to be Counter or OrderDict. From b) “order specified in the inheritance tuple preserves” follows that Counter has to go before OrderDict, because they list in the inheritance tuple of OrderCounter. So, Counter has to first element in MRO.

II. By proposition (2.0)

III.
So, currently we established that

(#) L[OrderCount]=OrderCounter, Counter,…,object.

We have following options:

1) L[OrderCount]=OrderCounter, Counter, Dict, OrderDict, object
2) L[OrderCount]=OrderCounter, Counter, OrderDict, Dict, object

there is no other possible that will not contradict (#) and consists of objects of given inheritance graph. Let’s write (1) in a bit explicit way:

1) L[OrderCount]=OrderCounter(Counter, OrderedDict), Counter(Dict), Dict(object), OrderDict(Dict), object

On one side, Dict is listed before OrderDict, on other side, we have OrderDict(Dict) and by a) “Children precede their parents” it follows that Dict should be listed before OrderDict. Contradiction.

Assuming that the given graph is linerizable (Python doesn’t emit error), it has to be 2), that is

L[OrderCount]=OrderCounter, Counter, OrderDict, Dict, object

and OrderDict is called before Dict in MRO.

The exact MRO algorithm is provided in theortical part. In practice, the best chance, you will never used it. First of all, you can ask the Python do it for you. X._mro_ will give you result of applying Method Resolution Order algorithm. If MRO for your inheritance graph can’t be computed Python will emit an Error.

But, what is more important, in order to reason about MRO it is sufficient to know about properties listed above. We just did it in 2 examples above.

***

For more elaborate example see https://en.wikipedia.org/wiki/C3_linearization or here https://www.python.org/download/releases/2.3/mro/

Raymond Hettinger claims, that using (cooperative) multiple inheritance in Python make use of Dependecy Injection unnecessary. You can watch his video here “Super considered super!”

https://www.youtube.com/watch?v=EiOglTERPEo

and read his blogpost here https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ (Python 3 syntax http://code.activestate.com/recipes/577720-how-to-use-super-effectively/). Note, however, he use incorrect definition of inheritance. Code re-use is the feature of mixin.

Multiple inheritance in Python is cooperative.

Based on http://code.activestate.com/recipes/577720-how-to-use-super-effectively/

Example 3

This example shows use of (cooperative) multiple inheritance to provide mixin. See separate article on what is mixin https://medium.com/@alex_ber/mixin-and-treats-94035022182c.

Based on https://en.wikipedia.org/wiki/Mixin#In_Python

In Python mixin implemented by using of inheritance.

In Python, the SocketServer module has both a UDPServer class and a TCPServer class. They act as servers for UDPand TCP socket servers, respectively. Additionally, there are two mixin classes: ForkingMixIn and ThreadingMixIn. Normally, all new connections are handled within the same process. By extending TCPServer with the ThreadingMixIn as follows:

the ThreadingMixIn class adds functionality to the TCP server such that each new connection creates a new thread. Alternatively, using the ForkingMixIn would cause the process to be forked for each new connection. Clearly, the functionality to create a new thread or fork a process is not terribly useful as a stand-alone class.

In this usage example, the mixins provide alternative underlying functionality without affecting the functionality as a socket server.

--

--

Senior Software Engineer at Pursway

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store