
I doubt that __as_parent__ solves the "arbitrarily deep"
Stephen J. Turnbull writes: problem (although you may be able to persuade me it will work "better" "most of the time"). The way i've implemented it rn, it is possible : __as_parent__ can take for argument any class in the inheritance tree of the class context it is called in: class An: def method(self): print('An') class A[n-1](An): pass ... class A1(A2): pass class A0(A1): def method(self): self.__as_parent__(An).method() A0().method() prints 'An' That's what i mean by 'being able to target any ancestors' On top of that, having to dive into the MRO to pass as argument of super the previous class in MRO rather than the one you're targeting feels like obfuscation, where no such mental gymnastic is needed with my __as_parent__ replacment. --
(a) there are people who use it for more advanced purposes and (b) it can't cause you any confusion in that case.
(a) What are those more advanced feature? On top of the classic inheritance features, I can only think of dependency injection in the inheritance tree. Such a feature could be replaced, although i didn't provide a replacment for this feature, it doesn't seem like it's gonna be hard to do, __as_parent__ could rely on a dict, which would hold as key a class, and as value a class too. If the targeted class is in the keys, target the corresponding value instead. This class could then inherit from the class it replaced, and you got your dependency injection. On top of that, it unties a lot more this dependency injection feature from the method resolution algorithm. If you can think of more features that i didn't account for, I wanna hear about it. (b) Wrong! The common use case for inheritance and super is what informs most people of its behavior, as of today, this knowledge you acquire turns out to be unaplicable in those multiple inheritance scenarios. I mean, it is possible for super to call classes on another branch of the inheritance tree. That is most definitely *not* what you learn from using super in simple scenarios. My alternative doesn't do those jumps, and i think overall matches more closely what an untrained mind (which none of us here are, don't get blinded by your knowledge) would expect. --
But C3/super assumes nothing of the kind.
At first i was gonna answer that it of course does make this assumption, but I think we aren't exactly talking about the same thing. I was saying that the assumption that it can make sense to order class inheritance trees lead to the use of C3 in python, which it turns out can't solve all scenarios. I think this proves that this assumption was wrong. I think what you are saying is that it doesn't make that assumption because it fails to produce an order in some scenarios. So overall, i think we agree on the matter of fact of today's C3 behavior, and overall, i think we agree that C3 is an incomplete solution? correct me if i'm wrong, i don't wanna misunderstand you. The solution i propose would be able to cover those cases C3 doesn't. Because it wasn't build around the idea of ordering. --
Pragmatically, C3 seems to give useful results frequently, unuseful or confusing results infrequently (except for people who don't understand C3 and randomly change code, who will be confused frequently)
Not knowledgable people should be accounted for to. Knowledgable people would benefit from an easier to use langage anyways. But yeah, I agree that the confusing behavior are not in the majority. They still exists. ---
It turns out that this is useful to quite a few Python programmers.
No matter what you feed a comunity, some people will make gold out of. What matters here is that if we change anything, the new world allows what the old one did. Feature wise, I mean. Do we have a list of use case / features provided by the current state of MRO + super? We would wanna keep those behavior in the alternative. To me, on top of the classic inheritance features, there's only the dependency injection in the inheritance tree. As explained above, it isn't hard to produce an alternative for this feature in the realm of my alternative to MRO + super. --
Have you specified when the ExplicitResolutionRequired error is raised
I didn't, my apologies It would be raised at method resolution time.
(No, I'm not going to read your code for the implementation, I'm interested in the specification.)
Fine by me, it's just a proof of concept anyways. Although, I dive into the reason why i came up with this idea and the specification, in the README. This might be worth a read. ---
You can't have "reliability" without disabling super, I suspect, since you can't prevent stdlib or 3rd-party code from using it, and I believe that means method resolution in that code will depend on MROs, even if you can specify the root of the class subtree explicitly. I doubt disabling super would be acceptable to Python core devs, and it would very likely make the stdlib less than useful to you.
Yeah, this is essntially the migration problem. It is still an open one to me. We can think of a few stuff, like adding a flag to be able to switch from one feature to the other. Since the simple cases wouldn't change much in their apparent behavior, if we introduce my change to super into super, essentially running the old super code, or my new version, based on that flag, and using C3 or my alternative EMR (which for now lives in __getattribute__) could be a way to allow people to transition from old super + MRO to __as_parent_ + EMR without much pain, at least in cases of simple inheritance. Idk, i'm just sharing ideas, this might not be the way to handle the introduction of that feature / migration if that's the goal --
I'm not sure I like "reliability" as a requirement. The point of MRO is that it is an attribute of the object
Well, it *has* to be a class attribute for it to work. my alternative can lie in __getattribute__, no need for an extra class attribute. Obviously, it feels weird to get rid of it, because today mro is so unobvious that having it has a class attribute is a must. but my solution just doesn't need. Actually such an attribute would make no sense in my alternative. --
But with a "reliable" __as_parent__, it's possible that a method defined in the child calls __as_parent(SmallestFirst).my_sort(), while a method in the parent calls __as_parent(LargestFirst).my_sort(). This may be a problem
I'm sorry, i completely fail to understand what you mean, can you provide a code example to illustrate what you mean? ---
I don't understand "ancestors as targets". If there's a name collision between grandparents, that will already raise ExplicitResolutionRequired. So a child of those grandparents must use __as_parent__, which resolves the problem in the default case. Do you just mean that if for this particular grandchild, you don't want the default resolution, you can use __as_parent__(grandparent_a)?
Yes. Since ExplicitResolutionRequired are raised only when resolving the method, grandparents can both present a method with the same name, which the parent would not necesserly redefine to resolve the conflict, if it doesn't intent on calling it. The child in this case would be stuck if __as_parent__(grandparent_a) was not an option
If the error is raised at method resolution time, what happens if you never try to resolve it? How about a case where the parent doesn't specify a resolution of a collision between grandparents, but no code tries to resolve that method? Shouldn't the child be allowed to use __as_parent__ there?
If you never try to resolve it, you don't get an error. When a parent doesn't specify a resolution for a collision in grandparents, the child is allowed to call __as_parent__ on the grandparent (it is allowed to do that in any case). Actually, it would need to, if it intend on resolving that method at some point to get out of the ExplicitResolutionRequired error ---
I don't much like your definition of "conflict". Mine is "when super() does the wrong thing", which (surprise!) it almost never does for me, and I've never used the two-argument form (except in my other post to you): class.method calls have always been adequate. I've never seen a case where I couldn't diagnose the need to override the MRO from the class declaration. Not saying problem cases don't exist, just that I'm perfectly happy with C3 MRO and super().
I think what i refer to as conflict is what you refer to as collision. Essentially, when two parent class provide a method with the same name, that's what i call a conflict. I agree that most scenarios are not problematic, essentially because most scenarios don't involve multiple inheritance. And that's why i made sure the external behavior of my alternative matches today's external behavior of super + MRO in those cases. Idk how often you are confronted to multiple inheritance, I personnaly am fairly often, for example with class based views in django, and It is not uncommon to have to place a mixin before the View class we wanna inherit from. A possible reason is that the View class has colliding methods with the mixin (maybe simply __init__) in which they don't do a call to super, as they are at the top part of the inheritance tree. Which in term, completely lose a branch of the inheritance tree __init__. When designing View, who can blame them for not thinking of that? On top of that, if the collision is not on a dunder method, adding a call to super in the top parent class would raise an error when called outside multiple inheritance, as its parent don't have those method, but in case of multiple inheritance, it's needed. How are anyone supposed to solve for that? I also wanna point out that most of us here are very familiar with the current state of MRO + super, and our easiness to work with it could show that we are knowledgable as much as it could show that the feature is easy to use. But it doesn't distinguish between those two explanation, so i'm not confortable with this argument.