Python MRO

Or How super() works

Python MRO - Or How super() works

presented by:Drew Smathers (drew.smathers@gmail.com)

Preliminaries

mro algorithm

Inheritance with Old-Style classes

class ClassicBeer:
    def __init__(self, ounces=12):
        self.ounces = ounces
    def drink(self): pass

class Ale(ClassicBeer):
    def __init__(self, ounces=10):
        ClassicBeer.__init__(self, ounces)
    def drink(self):
        ClassicBeer.drink(self)

Inheritance with New-Style Clases

class Beer(object):
    def __init__(self, ounces=12):
        self.ounces = ounces
    def drink(self):
        pass

class BW(Beer):
    "Budweiser"
    def __init__(self, ounces=16):
        super(BW, self).__init__(ounces)
    def drink(self):
        super(BW, self).drink()

Inheritance with New-Style Classes (cont.)

Demo: demo1.py

# Old style
>>> ale = Ale()
>>> ale.drink()
>>> ale2 = Ale(23)
>>> ale2.drink()
# New style
>>> bw = BW()
>>> bw.drink()
>>> bw2 = BW(45)
>>> bw2.drink()

super() is what?

It depends ... a bound or unbound super object.

Guido says you'll likely only find the second useful ... I agree.

super() is what? (cont.)

Examples

>>> super(str, 'hello')
<super: <class 'str'>, <str object>>
>>> super(list, [1,2,3])
<super: <class 'list'>, <list object>>
>>> super(object, list)
<super: <class 'object'>, <list object>>

super() is what? (cont.)

super() is ... confusing.

... Think of the super object as a proxy around self and a type A with a specific set of method bindings.

Multiple Inheritance the classic way

class PerishableMixin:
    def __init__(self, maxage=135):
        self.maxage = maxage
        self.age = 0

class Ale(ClassicBeer, PerishableMixin):
    def __init__(self, ounces=10, maxage=120):
        ClassicBeer.__init__(self, ounces)
        PerishableMixin.__init__(self, maxage)
    def drink(self):
        ClassicBeer.drink(self)

Multiple Inheritance with New-Style classes

class BW(Beer, PerishableMixin):
    "Budweiser"
    def __init__(self, ounces=16, maxage=120):
        super(BW, self).__init__(ounces, maxage)
    def drink(self):
        super(BW, self).drink()

Multiple Inheritance (cont.)

demo: demo2.py

Multiple Inheritance (cont.)

Multiple Inheritance (cont.)

demo: demo2b.py

Multiple Inheritance (cont.)

Our fix is really lame, because:

Multiple Inheritance (cont.)

Let's add a drink method on PerishableMixin.

class PerishableMixin:
    def __init__(self, maxage=135):
        self.maxage = maxage
        self.age = 0

    def drink(self):
        if self.age > self.maxage:
            print '[PerishableMixin] Blecccchhh'
        else:
            print '[PerishableMixin] Gulp'

Multiple Inheritance (cont.)

Multiple Inheritance (cont.)

class PerishableMixin(object): # make it new-style
    def __init__(self, maxage=135):
        self.maxage = maxage
        self.age = 0

    def drink(self):
        if self.age > self.maxage:
            print '[PerishableMixin] Blecccchhh'
        else:
            print '[PerishableMixin] Gulp'

Multiple Inheritance (cont.)

Multiple Inheritance (cont.)

demo: demo2f.py

Ahhh ... it works. But there are problems with the code.

Let's refactor it some.

Multiple Inheritance (cont.)

demo: demo3.py

Some patterns we've adopted:

Multiple Inheritance (cont.)

And the problems?? Oh, there are so many ...

>>> class Miller(Beer): pass
...
>>>> m = Miller()
...
<type 'exceptions.TypeError'>: int argument required
>>> Beer() # Certainly this works???
...
<type 'exceptions.TypeError'>: int argument required

A Closer look at the mro

demo: demo4.py

Notice this has the same problems as previous code.

A Closer look at the mro (cont.)

Roughly:

>>> class PerishableMixin(object): pass
>>> class Beer(object)
>>> class BW(Beer, PerishableMixin)
>>> class Syrup(object)
>>> class AJM(Syrup)
>>> class ATD(BW, AJM, PerishableMixin)

Note that ATD redundantly extends PerishableMixin - this is a hint as to why the mro for ATD is so weird.

A Closer look at the mro (cont.)

The mro() for class ATD from demo4:

A Closer look at the mro (cont.)

The mro maze for our gnarly example

The mro maze for our gnarly example

A closer look at the mro (cont.)

super() doesn't mean anything.

It certainly doesn't mean parent.

Dylan's terminology is next()

A closer look at the mro (cont.)

Some simpler examples (all with problems):

demo5f.py demonstrates a hack which will tell us if we're the last in the chain ... never write code like this ... please.

Diamonds in the Inheritance Heterarchy

>>> class A1(object): pass
>>> class A2(A1): pass
>>> class B1(A1): pass
>>> class B2(B1): pass
>>> class C(B1): pass
>>> class D(A2, B2, C): pass

Diamonds in the Inheritance Heterarchy

Criss-cross

demo5.py and working demo5b.py

Diamonds ... (cont.)

Runnning the demo we get:

D -> A2 -> B2 -> C -> B1 -> A1

Diamonds ... (cont.)

Criss-cross

The mro for demo5b.py

Inheritance Heterarchy without diamonds

Now something simpler (demo5c.py - demo5f.py)

>>> class A1(object): pass
>>> class A2(A1): pass
>>> class B1(object): pass
>>> class B2(B1): pass
>>> class C(object): pass
>>> class D(A2, B2, C): pass

Inheritance Heterarchy without diamonds (cont)

Ahhhh

The mro for demo5c.py - demo5f.py

Problems

Possible Solutions

Further Reading

Better explanations:

This presentation and scary examples:

The End

Questions?