Stephen J. Turnbull writes:
> I doubt that __as_parent__ solves the "arbitrarily deep"
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.
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/L2KXIO2W5KOFUSBJEEIMUAGTFRBFFYYZ/
Code of Conduct: http://python.org/psf/codeofconduct/