MAINT: unify usage of calls to parent classes, closes #1061 #1161
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Getting this out of the way.
Caveat
There are some cases when
super()
is the wrong choice, most notably with multiple inheritance and "conflicting" methods. Everything is good as long as a certain method only exists once or things stay nicely in order. However, consider the case ofScalingFunctional
which I stumbled over while doing this change.We have
class ScalingFunctional(Functional, ScalingOperator)
, with MROSo far, so good. The
__init__
method ofScalingFunctional
even hardcodes the__init__
calls to both parent classes, not usingsuper
. Everything should be fine, or?Turns out, no. The crucial part is the common misconception (also on my part) that in an instance method of a class,
self
always refers to an instance of that class. That's not true. In this case, whenFunctional.__init__
usesself
, thenself
is an instance ofScalingFunctional
. In particular, when usingsuper
to resolve the MRO, it is still the one ofScalingFunctional
.Hence, when
Functional.__init__
callssuper(Functional, self).__init__(...)
, then the__init__
method that is called is not the one ofOperator
(the only parent ofFunctional
), but the one ofScalingOperator
, because it comes first in the MRO.We can confirm this by printing
self.__class__.mro()
inFunctional.__init__
:Conclusion
We have to be careful with multiple inheritance the MRO contains potentially conflicting methods. As the documentation says,
super
is intended for collaborative classes, and this situation is not like that.Sometimes it's hard to tell who will call what. Two ways to mitigate this are:
super
in classes that may participate in non-collaborative multiple inheritance. That applies mostly to core classes. It may be hard to know in advance which classes will be affected.