Or How super() works
| presented by: | Drew Smathers (drew.smathers@gmail.com) |
|---|
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)
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()
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()
It depends ... a bound or unbound super object.
Guido says you'll likely only find the second useful ... I agree.
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 ... confusing.
... Think of the super object as a proxy around self and a type A with a specific set of method bindings.
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)
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()
demo: demo2.py
demo: demo2b.py
Our fix is really lame, because:
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'
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'
demo: demo2f.py
Ahhh ... it works. But there are problems with the code.
Let's refactor it some.
demo: demo3.py
Some patterns we've adopted:
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
demo: demo4.py
Notice this has the same problems as previous code.
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.
The mro() for class ATD from demo4:
The mro maze for our gnarly example
super() doesn't mean anything.
It certainly doesn't mean parent.
Dylan's terminology is next()
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.
>>> 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
demo5.py and working demo5b.py
Runnning the demo we get:
D -> A2 -> B2 -> C -> B1 -> A1
The mro for demo5b.py
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
The mro for demo5c.py - demo5f.py
Better explanations:
This presentation and scary examples:
Questions?