[New-bugs-announce] [issue47130] mro and super don't feel so pythonic
report at bugs.python.org
Sat Mar 26 12:10:06 EDT 2022
New submission from malmiteria <martin.milon at ensc.fr>:
Before anything, i made a github repository about this topic here : https://github.com/malmiteria/super-alternative-to-super
The core of what i wanna discuss here is that i don't think mro and super (mainly because it relies on mro) are very pythonic. Mainly that some behaviors of the mro are too implicit, and are silencing what really should be errors.
Let me explain :
in case of multiple inheritence, resolving a child method from it's parent isn't an obvious task, and mro comes as a solution to that. However, i don't understand why we don't let the programmer solve it. I think this is similar to a merge conflict, and not letting the programmer resolve the conflict feels like silencing an error. This is especially infuriating when you realise that mro doesn't solve all possible scenarios, and then, simply refuses the opportunity to solve it to the programmer.
Then, super relying on mro gives off some weird behaviors, mainly, it's possible for a child definition to affect what a call to super means in it's parent. This feels like a side effect (which is the 'too implicit' thing i refer to).
I also don't understand why we can't simply pass the parent targeted as argument to super, instead of having no argument, or having to pass the current class and instances as argument :
super(child) is a proxy to parent, when super(parent) would make more sense to be the proxy to parent, in my mind.
I dive in more depths about those topics in the readme of the github repository i linked at the top of this comment.
what i propose is a solution that would follow those rules:
The mro alternative, which i called explicit method resolution aka EMR (which is probably not a good name since i apply it, as mro, to all class attributes), follow those rules :
1) Straightforward case : the class definition has the method / attribute : this is the one EMR should resolve to
2) Not found : the method / attribute can't be resolved in the class itself, or by any of it's parents, then it should raise an AttributeError
3) Only on parent : the method / attribute can't be resolved in the class itself, and can only be resolved by one of it's parents, this is the one EMR should resolve to
4) Multiple parent : the method / attribute can't be resolved in the class itself, and can be resolved by at least 2 of it's parents, then an ExplicitResolutionRequired error should be raised
5) Transimittin errors : the method / attribute can't be resolved in the class itself, and one parent at least raises an ExplicitResolutionRequired error, then it should raise an ExplicitResolutionRequired error
6) (optional?) Single source : when multiple parent can resolve a method from a single source (in case of diamond shape inheritence), the ExplicitResolutionRequired is not needed
The super alternative, which i called __as_parent__ should follow those rules :
1) reliability : the target __as_parent__ points to should not depend on anything other than the argument passed to it
2) expliciteness : in case of multiple inheritence, the parent targetted should be passed as an argument to the __as_parent__ method.
3) impliciteness : in case of simple inheritence, it is not needed to specify the parent targeted (since there can only be one, and it make it closer to the actual behavior of super in most cases)
4) ancestors as targets : should be able to target ancestors, either direct or not (which is needed in case two grandparent define a method that a single parent share, there would be no other way to solve the ExplicitResolutionRequired otherwise)
this solution has a few advantages in my mind :
- the current mro and super are more tightly coupled than the emr and __as_parent__ i propose here
- the __as_parent__ is more reliable than super in its behavior, and should lead to an easier learning curve
- the emr i propose as a replacement to mro allows for some inheritence tree mro doesn't allow.
- the __as_parent__ method being able to target specific parent allows for different methods to visit the parents in different order easily, which today would be harder, since the parent visiting order is tightly coupled to the class definition
- with emr, in case of problematic resolution, an error is raised to tell you about the problem, and ask you for explicit resolution, the current solution doesn't, which can lead to surprises closer to production environment.
A few possible downsides :
- the transition would be a pain to deal with
- the current mro allows for dependencies injection in the inheritence tree. I believe this feature should be untied from super and mro, but it would require some work.
- the current super and mro are old enough to have been faced with issues and having been updated to solve those issues down the line. Any alternative would have to face those issues again and new ones down the line
- any coexistence between those two solution would require some work to make sure they don't break one another (i've explored some of those scenarios in my repository, but i definitely didn't cover it all)
I believe that what i talk about here is definitely too much at once.
For exemple, adding a kwarg to super to specify the parent it targets would be a very easy change to add into python, and wouldn't require much more of what i talk about here, but it would still have some of the value i talk about here.
But overall, it all makes sense to me, and i wanted to share it all with you guys.
What do you think? does it makes sense? Am i missing something?
title: mro and super don't feel so pythonic
Python tracker <report at bugs.python.org>
More information about the New-bugs-announce