Chris Angelico writes:
> I'm not sure what you DO expect, though. 

in this case :
```
class A:
  def method(self): pass

class B:
  def method(self): pass

class C(A,B): pass
```

it is unclear what C().method should resolve to. So instead of forcing a 
resolution, i'd prefer an error to be raised, stating that the resolution can't 
be done automatically. Hence the name ExplicitResolutionRequired.

This is what i would expect from calling C().method in this example.
Or at least, this is how my solution would behave.

--
> One of the reasons to use the MRO that Python currently does is that
it is perfectly possible and plausible to change the MRO by
subclassing again.

this is essentially dependency injection, I haven't implemented a solution for 
it on my repository, but this is not a hard problem. Essentially, I'll just 
need to add some remap dict that the __as_parent__ would be aware of, so i can 
tell the __as_parent__ to target the remapped class instead of the one it was 
given as an argument.
This remapped class can then inherit from the class it replaces, and you got 
your class dependency injection.
With the added bonus that this features is now less tightly coupled to method 
resolution.

> What is its
> definition of "conflict"?

When you're trying to acces a method that can be resolved in multiple parents 
(and isn't resolved in the child), that's a conflict.
I've seen other people here call it collision, whatever name is fine by me.


> In proper cooperative multiple inheritance,
> there's no such thing as a conflict;

Then i don't want proper cooperative multiple inheritance. Although, since you 
were asking me to clarify what i mean by conflict, maybe we don't disagree here?

> as the programmer, you choose
> which methods override and which methods augment, simply by creating
> those methods and choosing whether to call super's method or not.

super relies on mro, which you don't choose. Actively choosing which of the 
parents method you wanna extend or not will require you to work *around* MRO, 
not *with* it.
My __as_parent__ is given as argument which class it should be the proxy of.
__as_parent__(B) will always be a proxy of B, no matter what B's subclass MRO 
will end up being.
Except of course in case you *explicitely* decide to go for dependency 
injection, in which case a remap can be done for __as_parent__(B) to actually 
target remap(B) instead of B.


--
> Class A shouldn't have to care what other parents its children have.

I very completely agree with that. And with today's super and MRO, this is not 
possible. Not if you want to make sure all your childs will be able to behave 
like you'd expect (I'm not a dad yet, can you tell? xD)

> (Which, I have to say, would make Thanksgiving dinner an extremely
> awkward time.)
I laughed so hard at that one xD

> Which of these super() calls would, by your description, need to raise
> an error?

None, actually.
super should not raise the ExplicitResolutionRequired error, never.
Accessing a method from a child class, which don't redefine it, when multiple 
parent have it, should raise the error.

It's essentially an access error, not a definition error.
In my exemple of an implementation, this error is raised from a 
__getattribute__ call.

And, redefining the method in the child, which then can use super, or my 
alternative (since super relies on MRO and i'm producing an alternative to MRO, 
i need an alternative to super).
Using my alternative allows to select which parents method it should extend.
With a code example:

```
class A:
  def method(self): pass

class B:
  def method(self): pass

class C(A,B): pass
```

running the code ```C().method``` should raise the ExplicitResolutionRequired 
error.

Updating C's code like that :
```
class C(A,B):
  def method(self):
    self.__as_parent__(A).method() # calls method defined in A
    self.__as_parent__(B).method() # calls method defined in B
```

now running ```C().method``` works fine.
Note that with the use of super, you can't tell in the body of the C method how 
to combine A and B's methods.
You have to rely on super order of visiting those methods.
Meaning that in this specific example you'd need either to add a call to super 
in A, for it to call B's method, or a call to super(A, self) in C for it to 
call B's method, without having to change A's definition.

> It's far less obvious than you perhaps think, so please elaborate,
> please show exactly what constitutes an error.
I apologise if i wasn't clear, i hope it's better now.
I'll do my best to explicit my idea more in the future too.

--
> It seems like you're trying to augment superclass
> behaviour in a very complicated way

Actually most change i advocate for are to replace MRO, not super.
The most substantial change to super would be that i'd want it to be a proxy of 
the class it was passed as an argument.
Today, it is a proxy to the next class in MRO after the class it was passed as 
an argument.
Making it not having to rely on MRO.

I realise i wasn't clear on the meaning of conflict and when to raise an error. 
Hopefully i was clear enough in my exemples above this time. If not, please let 
me know.
Essentially my propoosal is two-fold : An alternative to MRO first, an 
alternative to super second. I guess we can add the alternative to class 
dependency injection too in the mix, since it would too require an alternative.

> "class C(B1, A, B2)", you could have some parts of what's currently in
> B happen before A, and other parts happen after A.

You can't always break a class down, especially when you import those class 
from a library.

--
> What is your __as_parent__(A) and __as_parent__(B) behaviour here?

__as_parent__(A) would still be a proxy of A
__as_parent__(B) would still be a proxy of B

__as_parent__ behavior is never impacted by the presence / absence of any other 
child.
This is simply not related, __as_parent__ purpose is to proxy a parent class, 
nothing else.
MRO don't affect its behavior.

Essentially duplicates are allowed within the realm of explicit method 
resolution + __as_parent__.
if you have
```
class A:
  def method(self):
    print('A')
class B(A):
  def method(self):
    print('B')
    self.__as_parent__(A).method()
class C(A):
  def method(self):
    print('C')
    self.__as_parent__(A).method()
class D(B,C):
  def method(self):
    print('D')
    self.__as_parent__(B).method()
    self.__as_parent__(C).method()
```

then, D().method() will print D B A C A.

You'll notice that any call to B's method will print B A.
No matter what context it's called from, no matter what child it has or not, no 
matter if it shares a parent and a child with the same class (there has to be a 
joke here).

The fact that the current super + MRO make it the norm to simply chop off the 
behavior of class B's method parent call to replace it by C's method behavior 
is literraly insane to me.

In what world extending a parent method means replacing part of it with some of 
another parent?
_______________________________________________
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/VJSF64F2DPWXCBCMUSPC66O5ODCGSJXK/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to