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.

_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/ZWYKVK3VR6T6QIVRUGFV3MD5WL5RUC5J/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to