
malmiteria writes:
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'
Of course it prints 'An', and so does class A00(A1): def method(self): super(A[n-1], self).method() A00().method() Equally of course, __as_parent__(An) is exactly what I mean by "arbitrarily deep". Note that all you need to identify A[n-1] in this case is its class definition, and that's probably the great majority of cases. To find out, all you need to do is type
class A00(A1): pass ... A00.__mro__ [A00, A1, ..., A[n-1], An]
so it's not hard to figure this out.
(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.
Yes, that's precisely what I have in mind. However, the application is declarative composition (I think I just invented that name), as "Python's super() considered super" demonstrates with the trivial class LoggingOD[1]. This is very powerful and expressive (when it works). At this time, it seems like C3 provides the largest set of cases where it works.
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.
Have you read "Python's super() considered super!"? I definitely get the impression that you have not understood it. The point of several of the examples is that by using super() you can allow a future subclass to insert a method implementation in the MRO without knowing either the future subclass nor the class that currently implements the method. There are constraints on it, but the MRO allows you to express many such dependencies in a straightforward declarative manner, whereas your dict implementation is going to require a lot of explicit plumbing in derived classes. Also, the MRO approach allows different classes to inject different methods, but you're going to need something more complicated than the simple class -> class mapping.
On top of that, it unties a lot more this dependency injection feature from the method resolution algorithm.
You write that like you think that's a good thing, or it's not already possible to do that. ;-)
(b) Wrong! The common use case for inheritance and super is what informs most people of its behavior,
I think that's irrelevant, since the most common case is single inheritance, and there's only one (sane) way to resolve methods in that case. Trying to extrapolate from that to behavior in a multiple inheritance context simply means you haven't thought about multiple inheritance.
My alternative doesn't do those jumps, and i think overall matches more closely what an untrained mind [...] would expect.
I think that's probably the worst possible criterion for systems design. "Principle of Least Astonishment", sure, but at some point you want your systems to have some power. In theory any 3rd grader could fly a plane with a Wii handset, but maybe it's a better idea to employ a trained pilot.
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
C3/super doesn't *assume* it makes sense. It proposes it, and then demonstrates that it satisfies the useful and intuitive properties of local precedence ordering and monotonicity. Experience shows it *obviously* *does* make sense in many cases, in particular with single inheritance. But there are also many cases where it makes sense with multiple inheritance.
which it turns out can't solve all scenarios. I think this proves that this assumption was wrong.
But the assumption that something needs to solve all scenarios to be useful and usable is *your* assumption, which is clearly *wrong*.
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?
Yes. Who cares?
The solution i propose would be able to cover those cases C3 doesn't. Because it wasn't build around the idea of ordering.
Sure, but it gives up on many useful cases of declarative composition, basically limiting it to sets of mixins with disjoint attribute sets. And in any case, super() also covers those cases, and a few more besides.
Not knowledgable people should be accounted for to.
Sorry, not by deleting features, please. Less knowledgeable people should be taught. That's all the accounting they need.
Knowledgable people would benefit from an easier to use langage anyways.
I guess it would be easier to *use* super's first argument if it were the target class instead of the class preceding it in the MRO. But then it would be hard, and to me more confusing, to express the constraint on the second argument. Not an obvious win.
But yeah, I agree that the confusing behavior are not in the majority. They still exists.
The question is do they exist among programmers who read the Library Reference. Seriously, if you're not going to read that for super, you deserve what you get. super() is obviously magic (all other ways to call a method require an explicit object (including class) to bind it to). Anybody who has been annoyed by def __init__(self, x): self.x = x and doesn't realize super() is magic and requires study is gonna Find Out.
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.
Declarative composition that works is gold by design, even if it only works half the time.
What matters here is that if we change anything, the new world allows what the old one did. Feature wise, I mean.
Your proposal does not, since it is specifically designed to prevent useful behavior that you find confusing, and does so in a draconian way. There is *zero* reason to error on C or D below, because it's completely obvious which parent's method is desired. class A(object): # parent specified for emphasis def method(self): pass class B(object): def method(self): pass def C(A, B): pass def D(B, A): pass I guess you could allow both your approach and super, but then you have the problem that the interactions are definitely going to be confusing.
Do we have a list of use case / features provided by the current state of MRO + super?
The features and two typical use cases are described in the Library Reference. Declarative composition is described in "Python's super() considered super!" Desiderata for the MRO and the C3 algorithm are described in https://www.python.org/download/releases/2.3/mro/. I'll grant the docstring for super is not very helpful, but it's very easy to find those other references. It doesn't speak well for you that you didn't check them.
We would wanna keep those behavior in the alternative.
You can't. That's the whole point of your proposal, to get rid of behavior you don't like.
To me, on top of the classic inheritance features, there's only the dependency injection in the inheritance tree.
Well, the rest of us think that's very important.
As explained above, it isn't hard to produce an alternative for this feature in the realm of my alternative to MRO + super.
I think it's harder than you think, as explained above. I don't see how your proposal works if you don't know the detailed inheritance tree of the child class, and it certainly won't be robust to refactoring in the way that super() is.
We can think of a few stuff, like adding a flag to be able to switch from one feature to the other.
Since we can already do everything with super() that we can with your approach, this adds complexity for no benefit.
Well, [__mro__] *has* to be a class attribute for it to work.
What? Of course it doesn't. It could be computed at each attribute access. And it, too, is magic (try '__mro__' in dir(subclass)).
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.
What? "Method [in fact, attribute] resolution order" is quite obvious. In a von Neumann architecture, there will be an order (although it might be non-deterministic). The question then is can we make it deterministic, in a "nice" way. C3 has a couple of nice properties: 1. Local precedence ordering: Where class C(A, B), C precedes A precedes B regardless of the inheritance trees of A and B. 2. Monotonicity: If C1 precedes C2 in the linearization of C, then C1 precedes C2 in the linearization of any subclass of C. That's about as good as it's going to be.
but my solution just doesn't need.
Which strongly suggests that super() has capabilities that __as_parent__() does not.
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?
It's very simple: two ancestor classes A, B, derived from list both provide an in-place 'my_sort', but they generate the reversed order from each other. A parent class C derives from A, B and uses __as_parent__(A).my_sort in a method bisect_search. Then the child class D uses __as_parent__(B).my_sort in one context and __as_parent__(C).bisect_search in another. Then the underlying list will be in different orders after my_sort vs. bisect_search. This does not happen if super() is used properly. Avoiding this is the responsibility of the programmer in your model.
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.
Yes, I understand that. super() resolves the collision without a conflict. Sometimes we may not like the particular resolution. Much of the time reordering the parent classes will give us what we want. When it doesn't, typically the two-argument version of super will do so.
Idk how often you are confronted to multiple inheritance,
All the time. Code I work on uses a lot of mixins.
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__.
I don't understand what you lose here. This is way too abstract to make sense.
When designing View, who can blame them for not thinking of that?
This usually isn't all that difficult to work around, though. You just derive a child of the View class that *does* use super() where needed.
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 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 have no idea what you're talking about. "Python’s super() considered super!" explains that you can interpose a "root" class whose only job is to stop super() from trying a further level of parents. In the case above, that root class would be WrappedView. If View doesn't def or inherit a method, WrappedView's implementation of that method can either assert, raise a handleable exception, or just no-op.
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.
System design is hard. I don't think super() makes it harder. If you don't need to allow composability when deriving new classes, don't use super(). If you do need it, most of the time it's trivial to use: just prefix the method call with "super()." This ensures consistency of method resolution and robustness against refactoring of a parent class. The only time it's hard is if you are being deliberately inconsistent (this is not a bad thing, just requires more knowledge). In that case you always have the option to hardcode the call to the method implementation in an appropriate ancestor, or take full advantage of the child's MRO (true, that's wizardry, but sometimes it's a useful option). I don't think __as_parent__ makes system design *harder*, but it does put substantial burden on the programmer to ensure consistency, and I suspect it requires more knowledge of the inheritance tree than use of super does in the majority of actual cases. Footnotes: [1] Of course LoggingOD provides nothing in recent Python since the current implementation of dict (also a Hettinger innovation IIRC!) already provides "insertion order is iteration order". But it does show how powerful declarative composition was before that dict implementation was introduced.