Wield Python's super() like a Jedi
rhettinger.wordpress.com
rhettinger.wordpress.com
I suppose similar criticism can be leveled against many other dynamic techniques, except that this one sacrifices more readability than I'm comfortable with.
I think in a case like the one you describe, where the direct ancestor's implementation is circumvented to access a different ancestor's implementation, it should be done by calling
super(Ancestor).someMethod()
That should improve understanding. class Shape:
def __init__(self, **kwds):
self.shapename = kwds.pop('shapename')
super().__init__(**kwds)
Better written like this? class Shape:
def __init__(self, shapename=None, **kwds):
self.shapename = shapename
super().__init__(**kwds)
It seems like there are some things about Python's which the author doesn't like, and he solves them by adding a lot of boiler plate to each of his classes. It's probably a better idea to just embrace the Python way or use a different language. def __init__(self, shapename, **kwds):
Or, in Python 3: def __init__(self, *, shapename, **kwds):
(making shapename a required keyword-only argument) def __init__(self, shapename, **kwds):
is not better written; it allows shapename to be passed positionally which makes cooperative inheritance fragile. def __init__(self, shapename=None, **kwds):
also allows that. kwds.pop('shapename', None)
Will get you what you're looking for. In [1]: def init(shapename=None, **kwargs):
...: pass
...:
In [2]: init(shapename='circle', **{'shapename': 'circle'})
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/home/teh/Desktop/<ipython console> in <module>()
TypeError: init() got multiple values for keyword argument 'shapename' >>> def init(**kwargs):
... pass
...
>>> init(shapename='circle', **{'shapename': 'circle'})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: init() got multiple values for keyword argument 'shapename'And OrderedDict isn't the only one. Maybe for some reason I would like to have an OrderedCounter where all the counts default to 42. So I do this:
class DefaultOrderedCounter(defaultdict, OrderedCounter):
pass
doc = DefaultOrderedCounter(lambda: 42)
doc.update('abracadabra')
Which results in: Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "c:\python32\lib\collections.py", line 507, in update
_count_elements(self, iterable)
File "c:\python32\lib\collections.py", line 63, in __setitem__
self.__map[key] = link = Link()
AttributeError: 'DefaultOrderedCounter' object has no attribute '_OrderedDict__map'
Whoops! Apparently defaultdict doesn't use super either. Of course a better way to do this would be to subclass DefaultOrderedCounter and just override the __missing__ method by hand, but that's not the point.The article goes into "How to Incorporate a Non-cooperative Class", which basically says "wrap it up in a proxy class". But that's not really going to work here, since the result would be two separate dicts, with the defaultdictwrapper methods operating on one dict, and the other methods operating on the other.
ExampleThough I agree there's something annoying going on there with the multiple inheritence.
class Shape(Root):
def __init__(self, **kwds):
self.shapename = kwds.pop('shapename')
super().__init__(**kwds)
class ColoredShape(Shape):
def __init__(self, **kwds):
self.color = kwds.pop('color')
super().__init__(**kwds)
I would personally just have written this somewhat similar to this: class Shape(Root):
def __init__(self, shapename):
self.shapename = shapename
super().__init__()
class ColoredShape(Shape):
def __init__(self, shapename, color):
self.shapename = shapename
self.color = color
super().__init__(shapename)
But then again I'm by no means what anyone could call a Python 'Jedi'. Can anyone explain to me why using kwds in this way would be better? Would it not lead to having to look up the declaration of the class for the exact names of arguments to pop() every time?Thus, ColoredShape.__init__ can't simply pass on shapename to the super method, because that might not be the argument expected by the next __init__ method in the MRO. Instead, each method needs to gracefully accept arbitrary arguments and then pass on whatever it received (minus what it consumed).
It seems that the examples are tightly coupled and favors more on inheritance than on composition.
IOW, if the decomposition of the classes is the same, then the solutions using composition or using multiple inheritance will have much in common.
Things should be as simple as possible, but no simpler :-)
In Go something like super() is superfluous.
Without this, Go requires you to design your class hierarchy in a way that is cleaner, but the super concept is still not superfluous -- because you do sometimes need to reference an ancestor's implementation in Go.
You embed an interface and then explicitly reference the embedded interface by name. The Go interface
type ReaderWriter interface {
Reader
Writer
}
is exactly like this Python class ReaderWriter(Reader, Writer):
pass
To get at a particular superclass implementation, you pass the class to super, like so: def read_and_write(self):
super(Reader).read()
super(Writer).write()
which in Go is: func (rw *ReaderWriter) ReadAndWrite() {
rw.Reader.read()
rw.Writer.write()
}
So it's not superfluous -- there's an equivalent syntax to super which involves referencing embedded interfaces. def read_and_write(self):
Reader.read(self)
Writer.write(self)
That is, if you want to get at a particular superclass implementation, then you just call it directly. super() is used when you're not calling a particular superclass, instead relying on Python to compute the correct "next method" for you."super(Reader).read()", if you actually try it, will just result in an AttributeError. This is because super(Reader) returns an unbound super object, not a bound super object that you can actually dispatch from. Why this advanced usage might be useful is beyond the scope of this thread, but if you're curious then just google for python unbound super and hit the Feeling Lucky button.
BTW your sighing is not necessary.
Nice to see this pattern being acknowledged somewhere else; I thought I was the only one that did that.
Contrasting dict.__setitem__ with super().__setitem__, the latter adds one function call. In addition, both forms require a builtin lookup and an attribute lookup.
In PyPy, much of the overhead of builtin lookups, function calls, and attribute lookups is automatically optimized away.
Edit: To be clear I would not advocate not using super() on account of this, as I said it'll eventually be fixed.
"The calculation depends on both the class where super is called and on the instance’s tree of ancestors."
I agree that the class's MRO can be computed ahead of time, but theoretically the instance's order could be different (if a class method changed the definition of __setitem__)
From the docs:
"super(type[, object-or-type])" <-- python2 doc, type is required
"super([type[, object-or-type]])" <-- type is optional in python3
Just looks like super/parent like in other languages. Did i miss something from not reading this?
Everything? Mostly the part where, Python being an MI language, a given class A has multiple parents. And these parents are a sequence, not a set, which define a Method Resolution Order, and that method resolution order usually isn't breadth-first either, so Python's super is a graph walker. And because it's explicit, any node in the graph can stop the walking.
This means permuting two parent classes can have significant impact on the semantics of the graph walking, therefore the results of calling super.
Here's a link to the Python 2 version of the examples: http://code.activestate.com/recipes/577721-how-to-use-super-...
So no, super really has changed, and the syntax in the article does not work with very old version of Python.
Oh come on, he's saying if you do the incredibly minor syntax adjustment that all of the actual meat of the article still works in Python 2.
It is actually one of the things that annoys me most about Python, I guess this is the first thing that actually tempts me with Python 3. Damn.