On Wed, Nov 15, 2017 at 01:49:03PM -0800, Neil Girdhar wrote:
> Sometimes I get MRO failures when defining classes. For example, if
>
> R < E, C
> B < S, E
> S < C
> Z < B, R
>
> Then Z cannot be instantiated because C precedes E in B and E precedes C in
> R. The problem is that when the inheritance graph was topologically-sorted
> in B, the order was S, C, E. It could just as easily have been S, E, C.
These are not equivalent:
B < S, E
B < E, S
so your comment that it could "just have easily" been S, E, C is not
generally true. Calling
C.method(self)
E.method(self)
in that order is not, in general, the same as calling them in the
opposite order.
> So, one solution is to add C explicitly to B's base class list, but this is
> annoying because B might not care about C (it's an implementation detail of
> S). It also means that if the hierarchy changes, a lot of these added
> extra base classes need to be fixed.
>
> I propose adding a magic member to classes:
>
> __precedes__ that is a list of classes. So, the fix for the above problem
> would be to define E as follows:
>
> class E:
> from whatever import C
> __precedes__ = [C]
As the author of E, why would I do that? I don't even know that B
exists, and even if I did know about B, by adding __precedes__ I risk
breaking classes X, Y and Z which expect C to precede E.
> Then, when topologically-sorting (so-called linearizing) the ancestor
> classes, Python can try to ensure that E precedes C when defining B.
How? You're glossing over the most important detail: *how* should Python
produce a consistant, monotonic, bug-free linearization of the
superclasses given an arbitrary number of (possibly conflicting)
__precedes__ constraints?
If __precedes__ is a hard constraint, then you have to deal with
inconsistent constraints *as well as* inconsistent inheritence orders.
If __precedes__ is just a suggestion, rather than a hard constraint,
then you have to deal with the cases where the actual MRO ignores the
suggestion, leading to contradiction between what the source says and
what the code actually does.
In either case, we know have an even more complicated set of inheritence
rules, with even more things that can go wrong. Getting the MRO for
multiple inheritence right is hard enough already. Python's current MRO
algorithm is the third, the first two were broken:
http://python-history.blogspot.com.au/2010/06/method-resolution-order.html
https://www.python.org/download/releases/2.3/mro/
Letting people mess with the MRO will surely lead to subtle and hard to
diagnose bugs.
The harsh truth is that sometimes you just do not have a consistent MRO
for a certain set of superclasses. Python refuses to let you use such an
inconsistent MRO, not because it is trying to be annoying, but because
such inconsistent MROs are broken.
If you know of an alternative MRO that gives correct results for the
class hierarchy you give above, please share.
--
Steve
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
--
---
You received this message because you are subscribed to a topic in the Google Groups "python-ideas" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/python-ideas/T7YNKZmwW1c/unsubscribe.
To unsubscribe from this group and all its topics, send an email to python-ideas...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Sometimes I get MRO failures when defining classes. For example, ifR < E, CB < S, ES < CZ < B, RThen Z cannot be instantiated because C precedes E in B and E precedes C in R. The problem is that when the inheritance graph was topologically-sorted in B, the order was S, C, E. It could just as easily have been S, E, C. So, one solution is to add C explicitly to B's base class list, but this is annoying because B might not care about C (it's an implementation detail of S). It also means that if the hierarchy changes, a lot of these added extra base classes need to be fixed.I propose adding a magic member to classes:__precedes__ that is a list of classes. So, the fix for the above problem would be to define E as follows:class E:from whatever import C__precedes__ = [C]Then, when topologically-sorting (so-called linearizing) the ancestor classes, Python can try to ensure that E precedes C when defining B.So it sounds like you are talking about the way that the siblings in the inheritance tree (the bases of each class) get "flattened" into the mro in a depth-first manner, and the relative order of siblings is not preserved.
What would you think about not topologically sorting the inheritance tree as such, but sorting a graph that has additional edges according to the base lists of each class involved? In this case those edges would be E->C, S->E, B->R. Is this what your talking about, or do I misinterpret the problem?
Steven D'Aprano wrote:
> These are not equivalent:
>
> B < S, E
> B < E, S
Not in general, but in many cases they will be, e.g. if
E and S have no method names in common. I think the OP is
implying that his case is one of those.
Maybe what's really wanted is a way to say "B inherits from
S and E, but it doesn't care what order they go in". Then
the MRO generating algorithm could in principle swap them
if it would result in a consistent MRO.
Or maybe the MRO generator could decide for itself if the
order of two base classes can be swapped by inspecting
their attributes to see if any of them clash?
--
Greg
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
On Wednesday, November 15, 2017 at 5:32:00 PM UTC-5, Koos Zevenhoven wrote:Sometimes I get MRO failures when defining classes. For example, ifR < E, CB < S, ES < CZ < B, RThen Z cannot be instantiated because C precedes E in B and E precedes C in R. The problem is that when the inheritance graph was topologically-sorted in B, the order was S, C, E. It could just as easily have been S, E, C. So, one solution is to add C explicitly to B's base class list, but this is annoying because B might not care about C (it's an implementation detail of S). It also means that if the hierarchy changes, a lot of these added extra base classes need to be fixed.I propose adding a magic member to classes:__precedes__ that is a list of classes. So, the fix for the above problem would be to define E as follows:class E:from whatever import C__precedes__ = [C]Then, when topologically-sorting (so-called linearizing) the ancestor classes, Python can try to ensure that E precedes C when defining B.So it sounds like you are talking about the way that the siblings in the inheritance tree (the bases of each class) get "flattened" into the mro in a depth-first manner, and the relative order of siblings is not preserved.It is preserved, but there are insufficient constraints, which causes problems with future class definitions.What would you think about not topologically sorting the inheritance tree as such, but sorting a graph that has additional edges according to the base lists of each class involved? In this case those edges would be E->C, S->E, B->R. Is this what your talking about, or do I misinterpret the problem?That's already part of the topological sorting algorithm. You have to do that. I'm suggesting additional constraints.I'm talking about the initial graph to be sorted. Not some intermediate graph that may be used for describing steps in an algorithm.
Anyway, the contraint given by the edge E->C is not yet part of the algorithm, because if it was already there, you wouldn't need "__precedes__" to add that edge.
But another thing that affects this is whether we consider multiple occurrences of a class in the inheritance tree as the same node in the inheritance graph or not. If yes, then the inheritance tree and the inheritance graph are two different things.
––Koos
--
It doesn't -- that's the point.
Currently it's assumed that the order base classes appear
in a class statement is the order that they must appear
in the MRO.
But that's not always true.
I'm suggesting that
the MRO algorithm should not assume that and should make
its own assessment of the required ordering.
Steven D'Aprano wrote:
> These are not equivalent:
>
> B < S, E
> B < E, S
Not in general, but in many cases they will be, e.g. if
E and S have no method names in common. I think the OP is
implying that his case is one of those.
Maybe what's really wanted is a way to say "B inherits from
S and E, but it doesn't care what order they go in". Then
the MRO generating algorithm could in principle swap them
if it would result in a consistent MRO.
Or maybe the MRO generator could decide for itself if the
order of two base classes can be swapped by inspecting
their attributes to see if any of them clash?
--
Greg
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
Cheers,
Nick.
--
Nick Coghlan | ncog...@gmail.com | Brisbane, Australia
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
If you wanted to pick up cases where two classes generate inconsistent
MROs that will prevent mutual subclasses (like "class B(S, E)" vs
"class R(E, C)"),
that feels like a job for a type checker, since it
isn't obvious whether it's the definition of B or the definition of R
that should be changed to reorder their MRO.