ROUND 2 @everyone

this is... a lenghty post to say the least.
That should give you the 'full' picture.
It's quite a lot of things to talk about, so i'm asking you to please 
explicitely state what proposal you are adressing when responding to this email.
If possible with a "CONTEXT : ..." line somewhere, so i can easily find it.
If it is unrelated to any proposal, feel free to be explicit about it in this 
CONTEXT: ... line

I apologise for eventual typos, i didn't wanna let you wait too long, and this 
took me way too long to write that already.

I'm copy pasting it down below, but it is also accessible here : 
https://github.com/malmiteria/super-alternative-to-super/tree/master/conversation

A quick TLDR:
there's 5 proposal, i've tried to make them independant of one another, but 
they would have to come in order (as far as i can tell)
1) a dedicated module for dependency injection and more called alterhitance
2) a way to declare inheritance after defining the class (postponed inheritance)
3) a solution to the diamond problem
4) changes to the proxy feature of super (that i already mentionned earlier in 
this thread)
5) changes to the method resolution algorithm (that i already mentionned 
earlier in this thread)


=========
The current state of MRO + super is being used in multiple ways.
No explicit feature is isolated for any of those use cases.

Let's list those feature. I'll dive in more depth about illustrating them, and 
my thoughts about them later.

feature 1 (of super alone): proxying the parent.
What most people think super does (and expect it to do):
it allows to call method from *the* (most people don't think of multiple 
inheritance) parent.
It can be used by working around MRO to proxy any parent directly.
I'd argue this isn't reliable, which makes it an incomplete feature, but that's 
still a use made of super
If i had to specify only one problem i have with super today :
 it's use in simple inheritance hints at a behavior of super, which turns out 
to be widely different from its actual behavior.
 a feature more consistent on that regard would make the langage much easier to 
learn, for everyone.

feature 2 (of super + MRO): sideway specialization / mixins
It however can jump sideways in the inheritance tree.
allowing for same level class to still specialize one another.
For this scenario, today, we rely on the order in which class are inherited.
This is usefull when for some reason it is not possible to make a class inherit 
from another at definition time.

feature 3 (of super + MRO): dependency injection / inheritance tree alteration
If you wanna inject a class wherever in some pre existing class inheritance 
tree, you can through the use of some clever new classes.
If you wanna deny its parent to one class, and essentially prune of branch off 
the inheritance tree, you can too.

feature 4 (of MRO alone): method resolution
when trying to resolve a method / attribute of a class, if not present in its 
definition, looks up the parent(s) to come up with an answer.
If no parent can resolve it, you get the attribute error, if at least one does, 
you get it.
I'd argue that it makes it possible to silently ignore a parent method when 
multiple parent define such a method. Which makes it especially painfull to 
debug those scenarios, which makes it an incomplete feature.
On top of that, it renders some class definition invalid, when really, the 
method resolution shouldn't break more than the method resolution. This should 
be a gettribute error, not a class definition error.


"feature" 5 : diamond problem.
Not really a feature, more a lack of feature. Today's diamond problem is solved 
by making the top layer appear only once in scpecialisation, which is a direct 
consequence of ordering the inheritance tree. Whatever changes we decide on the 
4 previous features, the impact on the diamond problem will either be null, or 
make the top layer be specialsed by all it's chilren. Either strategies aren't 
covering all needs. This requires an extra dedicated feature.

Also, as mentionned, i'd argue that some of those feature aren't fully 
delivering what they promise.
Essentially, they compromise for the extra feature, which in the context of a 
lack of properly isolated implementation for those feature seems fair.



# Why am i here?

What motivated me first here was the few problems with the feature 1 (proxying 
the parent) and feature 4 (method resolution) which I consider to be suboptimal.
Of course, the different use cases made out of the current super + MRO should 
be preserved.
Essentially, I do *not* intend to lose any strength / any use case of the 
langage
Listing all the use cases made out of current super + MRO matters then. And I 
think i already cover the most important ones with the 4 features i described.
If you feel I'm missing something, please let me know.

## My goal :
essentially, producing features for parent proxying and method resolution that 
wouldn't exhibit the flaws of their current version is my goal.
I also (of course) wanna preserve all current use case made out of the current 
version.
Since MRO is the base building block on which super relies, and MRO + super are 
the bases for those extra feature, it follows that I need to provide 
alternative for those feature.
Those changes would be massive, and require a lot of effort / time to make 
their ways into python, so it also matters to take this into consideration, at 
some point.
As much as possible, I don't wanna change the external behavior / API of the 
code. If my alternative looks the same, it should behave the same. If it 
behaves the same, it should look the same. Some amount of changes are needed of 
course, but my goal is to make it an easy transition, as much as possible.


Breaking changes?

feature 1 (proxying the parents)
  Any alternative that don't rely on MRO is bound to make the other features 
break, so I guess there's no way out this being a breaking change, if not for 
spreading alternative not reliant on MRO for those other extra features. But it 
would be possible to have a non breaking change that introduce the alternatives 
feature 2 and 3, followed after transition of code to those feature by another 
non breaking change of this feature 1, which would help the migration. However, 
unmigrated features 2 and 3 are bound to break.

feature 2 (sideway specialization) 
  Introducing an alternative without removing the old ways of doing things 
won't be a breaking change. This could be published in a first batch, with time 
to let every new commers learn to use it, and old timers switch to it. It would 
obviously need to be robust enough to suit everyones need, but that's only a 
matter of designing the feature.
  Removing the current version of this feature would come from removing / 
drasitcally change MRO + super current behavior, and can't really be done 
without such a change, so I'd say that feature 2 new alternative can be done 
without breaking change.
  Feature 1 breaking change would also break the current version of feature 2

feature 3 (dependency injection / inheritance tree alteration) :
  Same as for feature 2.

feature 4 (method resolution)
  Any alternative to it is bound to breaking changes, since MRO is so tightly 
coupled with feature 1 2 3.
  Those features alternative should come first. If possible, let's limit it to 
one breaking change total :p
  Once the alternatives to feature 2 and 3 are here, an alternative to feature 
1 can be produced, without removing the current version of feature 1 2 and 3.
  unlike for feature 1 2 and 3, I don't think it's possible to produce an 
alternative to feature 4 without replacing it. I might be wrong tho, at least 
for part of the feature.

Essentially : produce alternatives to feature 1 2 and 3 (most urgently 2 and 3) 
without removing the current version of those feature, then the alternative to 
feature 4 can be done, which would be the *only* breaking change 


# code examples:

## diamond tree, repeat top
CONTEXT : This exemple makes use of today's features.
No replacement feature is used there
The goal is to showcase today's behavior, and add my remarks.

```
class HighGobelin:
    def scream(self):
        print("raAaaaAar")

class CorruptedGobelin(HighGobelin):
    def scream(self):
        print("my corrupted soul makes me wanna scream")
        super().scream()

class ProudGobelin(HighGobelin):
    def scream(self):
        print("I ... can't ... contain my scream!")
        super().scream()

class HalfBreed(ProudGobelin, CorrupteGobelin):
    def scream(self):
        # 50% chance to call ProudGobelin scream, 50% chance to call 
CorruptedGobelin scream
```

when writing down the class HalfBreed, we expect the behavior of 
HalfBreed().scream() to be one of its parent behavior, selected at random with 
50% chance each.
with the use of super, it could look like that, intuitively


```
class HalfBreed(ProudGobelin, CorrupteGobelin):
    def scream(self):
        if random.choices([True, False]):
            super(HalfBreed, self).scream()
        else:
            super(ProudGobelin, self).scream()
```
However, super(HalfBreed, self) does not deliver ProudGoeblin behavior, but a 
mix of ProudGobelin, CorrupteGobelin, and HighGobelinBehavior.

We would expect the output to be : 

"I ... can't ... contain my scream!"
"raAaaaAar"

But it is :

"I ... can't ... contain my scream!"
"my corrupted soul makes me wanna scream"
"raAaaaAar"

Getting the correct behavior requires to let go of super, in such a way:


```
class HalfBreed(ProudGobelin, CorrupteGobelin):
    def scream(self):
        if random.choices([True, False]):
            ProudGobelin.scream(self)
        else:
            CorrupteGobelin.scream(self)
```

Which is an option multiple of you pointed out to me.
This options however is flawed in muliple ways, mainly because it looses 3 of 
the 4 features i describe earlier.
 - we lose the proxy feature, which to be fair, is aqueen to syntactic sugar. 
But this is still a feature loss.
 - we lose the sideway specialisation, coming from super's behavior, which, in 
this case, is the goal, so we're not complaining.
 - we lose the possibility of class dependency injection, since today, it 
relies on a consistent use of super.

As some of you mentionned (with pain and agony, for some of you ;) ), loss of 
feature is a big problem. I think at least for this scenario, assuming that we 
indeed managed to produce an alternative to all those feature, we are gaining 
the feature you are defending, not losing it.

## Can't assume on parent's more specialised.
CONTEXT : This exemple makes use of today's features.
No replacement feature is used there
The goal is to showcase today's behavior, and add my remarks.

```
class Glider:
    def push(self)
        print("I can't move so good on the ground")

    def jump(self):
        print("I'm free! I can fly now!")

class Wheel:
    def push(self)
        print("let's roll !")

    def jump(self):
        print("oh damn, i'm falling fast!")

class WheelGlider(Wheel, Glider):
    def push(self):
        # calls Wheel push method first
        # calls Glider push method second

    def jump(self):
        # calls Glider push method first
        # calls Wheel push method second
```

In this example, we expect WheelGlider push method to output :
"let's roll !"
"I can't move so good on the ground"

and we expect WheelGlider jump method to output :
"I'm free! I can fly now!"
"oh damn, i'm falling fast!"


this could be achieved in such a way:
```
class WheelGlider(Wheel, Glider):
    def push(self):
        super().push()
        super(Wheel, self).push()

    def jump(self):
        super(Wheel, self).jump()
        super().jump()
```

This raises a lot of questions
 - how do those jump and push method behave in case Wheel and Glider are 
refactored to inherit from a parent?
   - If this refactoring introduces the diamond case, we're back at the problem 
showcased in the diamond tree example
   - If no diamond case (no parent is shared by both, no matter how deep in the 
inheritance tree), then it would behave consistently with what's expected now.
 - How do those jump and push method behave in case WheelGlider is used as a 
parent of another class, which might or might not inherit from Wheel or Glider?
 - The API here is suboptimal, to say the least, since we have to pass 
arguments to super that will then be processed for us to reach our target. 
super(Wheel, self) proxies Glider, which is far from being obvious.
 - How does one uses all the features of super + MRO here? I don't see a case 
where you'd want sideway specialisation in this scenario, but it doesn't mean 
it wouldn't happen. But what about dependency injection / inheritance tree 
alteration?

Let's investigate :
Let's say you wanna make use of dependency injection to mock WheelGlider 
parents in a unit test setting.
The way to reach this goal today is simple, make use of super "weird" behavior 
in diamond scenario cases to inject your class in the middle.

```
class MockedWheel(Wheel):
    def push(self):
        print("mocked wheel push")
    def jump(self):
        print("mocked wheel jump")

class MockedGlider(Glider):
    def push(self):
        print("mocked glider push")
    def jump(self):
        print("mocked glider jump")

class MockedWheelGlider(WheelGlider, MockedWheel, MockedGlider):
d    def push(self):
        print("mocked WG push")
        super().push()
    def jump(self):
        print("mocked WG jump")
        super().jump()
```

To get some bird eye view of this inheritance tree, we know MRO will order it 
like that:
(1) MockedWheelGlider < WheelGlider < MockedWheel < MockedGlider
(2) WheelGlider < Wheel < Glider
(3) MockedWheel < Wheel
(4) MockedGlider < Glider

after a quick sandbox test, the complete mro is:
MockedWheelGlider < WheelGlider < MockedWheel < Wheel < MockedGlider < Glider 
(< object)

This happens to work fine:
```
MockedWheelGlider.jump() # prints "mocked WG jump", then "mocked glider jump", 
then "mocked wheel jump"
MockedWheelGlider.push() # prints "mocked WG push", then "mocked wheel push", 
then "mocked glider push"
```

If one of the super were replaced by the class.method approach, the 
MockedWheelGlider would fail to fully mock Wheel and Glider.
But as far as i'm concerned, this feature is quite robust in this scenario 
(when super is consistently used).

For deeper inheritance tree alteration, such as removing a branch (can be done 
by messing with __bases__), they could break some super calls (which is to be 
expected anyways)

Other than that, I would argue that for the sake of symetry, it would probably 
be preferable for super to allow / disallow the exact same API for the use of 
super, no matter the parent it targets.
Since in this scenario, we explicitely *don't* think of one parent as coming 
after the other (in term of specialisation, that is), it shows a disconnect 
between the feature and its use. Which i take as a hint that there is a feature 
to produce to match this need more accurately.


## Way too big combinatory possibilities
CONTEXT : This exemple makes use of today's features.
No replacement feature is used there
The goal is to showcase today's behavior, and add my remarks.

let's say you're making a web framework.

You might wanna provide a few View classes:

```
class View:
    def render(self):
        # does some generic view stuff
        print("view")
class ListView(View):
    def render(self):
        # does some list view specific stuff
        print("list view")
        super().render()
class FormView(View):
    def render(self):
        # does some form view specific stuff
        print("list view")
        super().render()
```

That's just an example, but you might get more of those View classes depending 
on the scenarios you wanna cover

You might also end up providing more features, that relate to the View, such as 
a LoginRequiredMixin, and a PermissionMixin
```
class LoginRequiredMixin:
    def render(self):
        # does login specific stuff
        super().render() # notice the call to super, despite the class not 
having parents

class PermissionMixin:
    def render(self):
        # does permission specific stuff
        super().render() # notice the call to super, despite the class not 
having parents
```

Again, those are just examples, you might end up needing more of those mixins.

Now, each View class would benefit from a variant with the permission mixin's 
behavior, a variant with the login required mixin, and a variant with both.
Add more mixins, and the combinatory explodes. And you'd want to produce all 
those classes variant for each View classes.

The "proper" way to produce those variant would be through inheritance. After 
all, those variant are simply "more specialised" variants of the base class.
In order to do it, you would have to produce a LoginRequiredView which inherit 
from View, a PermissionView inheriting from View, and a 
LoginRequiredPermissionView which would maybe inherit from PermissionView?

But the problem would then be, how do you not repeat yourself a million time, 
writting down the login specialisation code in each variant that requires it?
You can't really, if you wanna stick with inheritance at this stage.


However, the use of mixin, such as describe in the code blocks allows a DRY 
approach, if used correctly by whoever integrates those classes later on.

For an end user, such an integration could look like:

```
class MyView(LoginRequiredMixin, PermissionMixin, View):
    # would call render in some appropriate method, but not necesserly redefine 
it.
```

which would provide the expected behavior, with no need for the framework 
editor to produce all the possible combination, and allows for a DRY approach.


Essentially tho, this is a use case of multiple inheritance to provide the 
behavior of what really could have been simple inheritance.

The inheritance tree in this scenario is:

Login    Permission    View

     \        |       /

           MyView

When it fact it was meant to be

View
  |
Permission
  |
Login
  |
MyView


The practical reason is valid however, it is to me a symptom of a missing 
feature.

Turns out it is not possible today (or at least, not properly integrated in the 
langage) for a class to be defined without knowing its parent, and later on to 
be attributed parents.
It is also not possible to define deep layers of it's inheritance tree when 
defining it.
I'll be making a proposal for that later on.




# Let's talk about the features

## Proxying the parent
CONTEXT : I'm talking here *exclusively* about the parent proxying feature.
I will talk about the other features on their dedicated sections.
I'm describing the current state of this feature, to the best of my 
understanding.
I'll also add my remarks on what we would wanna keep / get rid of for an 
optimal feature.
But i am not at this point proposing an alternative.


This feature allows to proxy a class, which render it's method (and attributes 
to some extent) accessible from the parent to the child, "as" the parent.

```
class Dad:
    def say_hi(self):
        print("Good morning")

class Child(Dad):
    def say_hi(self):
        print("....")
        super().say_hi("Good morning")
```

This is a way to access the parent method as if through an instance of it.

It is made to be used inside class method definitions, more than anything else, 
but can be used to define child attribute based on parent attributes.

```
class Dad:
    age = 100

class Son(Dad):
    # super().age fails with a RuntimeError (no argument)
    # super(Son, Son) fails here with a NameError (Son is not defined yet)
    pass

Son.age = super(Son, Son).age - 30
```

The 'syntactic sugar' aspect of super's proxying feature is that calls to 
parent methods through super don't require to pass the instance as first 
argument, which makes for less redundant code. This should be kept in the 
alternative feature.

There is a 'disconnect' between the argument passed to super (when needed) and 
the class it will target to be a proxy of.
super(Son) is not a proxy of class Son, it is a proxy on the next in MRO (it 
feels redundant saying MRO order, but weird just saying in MRO ...) So in cases 
where the automatic solve of the argumentless super doesn't suits our needs, 
such as in the exemples above, there is some mental gymnastic, and knowledge to 
be known to reach our goal. This should be simplified in the alternative 
feature.

The possiblity of calling super with no argument is quite handy, and should 
probably be kept too.

In case of multiple inheritence, the argumentless expression of super does lack 
explicitness to me. I would wanna make the use of the alternative more 
explicit, in this scenario.

TLDR:
 - keep :
  - syntactic sugar
  - argumentless option in case of simple inheritance
 - don't keep:
  - argument and proxy target being not the same
  - argumentless syntax in multiple inheritance


## Sideway specialisation / Mixins
CONTEXT : I'm talking here *exclusively* about the sideway specialisation / 
mixin feature.
I will talk about the other features on their dedicated sections.
I'm describing the current state of this feature, to the best of my 
understanding.
I'll also add my remarks on what we would wanna keep / get rid of for an 
optimal feature.
But i am not at this point proposing an alternative.


Today, it is possible to have a class designed to inherite from another, but 
without having it explicitely inherit from it, when defined.

This is useful in case the specialistion class should be applied to multiple 
base class, especially when more specialisation classes exists, and all 
combination of the specialisations should be provided.
This is showcased in the "way too many combinatory" exemple i gave earlier.

If you have those base classes :
```
class View1:
    def render(self):
        # ...

...

class View10:
    def render(self):
        # ...
```

And those specilaser mixins:
```
class Spec1:
    def render(self):
        # ...
        super().render()
        # ...

...


class Spec10:
    def render(self):
        # ...
        super().render()
        # ...
```

The amount of combination is insanely huge, something like, if i'm not mistaken:

for n in range(amount of spec):
    (amount of views) * [(amount of specs)!/(n!)]

which in this case gives 62353000
Anyone attempting to manually do that would be insane. Obviouly, it's rare to 
have 10 specs, but the number is still high with 3 specs : 90
More accessible, but definitely not a fun task

How is it done then? Through multiple inheritance.
```
class MyView(Spec1, Spec4, View3):
    pass
```
is a class that inherits from what would have been the combination Spec1 
inherits from Spec4 inherits from View3, would it have been possible to define 
such inheritance between the specs and view.
Since MRO in this case is MyView < Spec1 < Spec4 < View3, any call to super 
from Spec1 will proxy Spec4, and any call to super from Spec4 will proxy View3.


Today's super allows to provide all those combinations, without having to 
explicitely define any combination
This feature is a must keep

I would argue tho, that today's solution produces a multiple inheritance tree, 
when all it need in fact is a simple inheritance tree.
It essentially "forget" about the inheritance it meant to provide, but falls 
back into working thanks to the ability of super to side jump.
An alternative would probably benefit from rendering the inheritance feature 
explicit. Today, those scenarios are simply denying the inheritance they really 
meant to benefit from. Let's make them actual inheritance.

I would also argue that multiple the inheritance syntax is definitely not 
appropriate, as it is very not clearly keeping track of the order of classes 
the child inherits from.
I've seen people try to reorder the classes in such a scenario, because it was 
weird to them to have mixins come after the view class. Which is the most 
important one, so they felt it made sense to have it placed first. Of course 
our automated tests made it clear it wasn't working, but it also means that had 
we not have tests, testing the mixin features, we would have silently lost this 
feature.
And testing those feature was debated in our team, since those are essentially 
the web framework responsibility, not ours.
I can see this bug reaching production for this reason.
As much as this is an UX consideration, this is definitely a must get rid of. 
An alternative should be designed to make it clear there is an order.

TLDR:
 - keep:
  - Not having to explicitely define all combinations of (n) mixins + feature 
class
 - don't keep:
  - Loss of simple inheritance when it's the dedicated feature for this need
  - Current UX for this scenario lack of explicit order


## Dependency injection / inheritance tree alteration
CONTEXT : I'm talking here *exclusively* about the dependency injection / 
inheritance tree alteration feature.
I will talk about the other features on their dedicated sections.
I'm describing the current state of this feature, to the best of my 
understanding.
I'll also add my remarks on what we would wanna keep / get rid of for an 
optimal feature.
But i am not at this point proposing an alternative.


Today's MRO + super allows for dependency injection.
Let's take the "Can't assume on parent's more specialised." example:
```
class Glider:
    def push(self)
        print("I can't move so good on the ground")

    def jump(self):
        print("I'm free! I can fly now!")

class Wheel:
    def push(self)
        print("let's roll !")

    def jump(self):
        print("oh damn, i'm falling fast!")

class WheelGlider(Wheel, Glider):
    def push(self):
        super().push()
        super(Wheel, self).push()

    def jump(self):
        super(Wheel, self).jump()
        super().jump()
```

MRO for WheelGlider here is : WheelGlider < Wheel < Glider

This order will never change, however, it is possible to squeeze between any 
two consecutively ordered classes by making use of a good understanding of MRO:

MockedWheelGlider < WheelGlider < MockedWheel < Wheel < MockedGlider < Glider
implies:
WheelGlider < Wheel < Glider

This new order can be obtained with:
```
class MockedWheel(Wheel):
    def push(self):
        print("mocked wheel push")
    def jump(self):
        print("mocked wheel jump")

class MockedGlider(Glider):
    def push(self):
        print("mocked glider push")
    def jump(self):
        print("mocked glider jump")

class MockedWheelGlider(WheelGlider, MockedWheel, MockedGlider):
d    def push(self):
        print("mocked WG push")
        super().push()
    def jump(self):
        print("mocked WG jump")
        super().jump()
```

This features essentially allows to reparent any class, anywhere in any 
inheritance tree, this is definitely a must keep.

I would argue that, as much as this feature is very reliable today, it relies 
on super and MRO which don't always cover developpers needs. Today's 
alternative class.method does most definitely not allow for proper dependency 
injection, as super calls implictely target the next in MRO, and class.method 
are hardcoded to target the same method no matter MRO, so the dependency 
injection dependance on super and MRO is a downfall of this feature.

This feature also requires to create a new class, inheriting from the class to 
inject dependency on, meaning it is not an inplace change.This should probably 
be kept like that. Although, i guess it doesn't hurt to give the option to the 
developper to have the change inplace or not.

The current state of this feature UX is quite poor tho, as the amount of 
knowledge you have to pour into the code to make it work like you'd expect is 
quite high. Making this feature not accessible to most developpers. The 
alternative would most definitely benefit from a simpler API.

On the topic of the link between dependency injection and super + MRO, it is 
important to note that any slight change on MRO + super could have an impact on 
this feature, essentially locking super and MRO in their current state.

TLDR:
 - keep:
  - the ability to reparent any class anywhere in any inheritance tree.
  - the not inplace change.
 - don't keep:
  - the reliance on super + MRO
  - the amount of knowledge needed to make use of this feature


## Method resolution
CONTEXT : I'm talking here *exclusively* about the method resolution feature.
I will talk about the other features on their dedicated sections.
I'm describing the current state of this feature, to the best of my 
understanding.
I'll also add my remarks on what we would wanna keep / get rid of for an 
optimal feature.
But i am not at this point proposing an alternative.


Method resolution is the feature that allow child class to access parent method 
as if it was their own, assuming they do not override it with a method with the 
same name.
```
class Dad:
    def joke(self):
        print("I'm afraid for the calendar. Its days are numbered.")

class Son(Dad):
    pass
```
In this exemple ```Son().joke()``` will resolve to joke method defined in Dad

However:
```
class Dad:
    def joke(self):
        print("I'm afraid for the calendar. Its days are numbered.")

class Son(Dad):
    def joke(self):
        print("*UNO reverse card sound* Hi dad, I'm son.")
```
In this exemple ```Son().joke()``` will resolve to the joke method defined in 
Son
Had Dad not have a joke method, an AttributeError would have been raised

In case of multiple inheritance, we still want the child to be able to inherit 
its parent methods.
```
class Dad:
    def joke(self):
        print("I'm afraid for the calendar. Its days are numbered.")

class Mom:
    def joke(self):
        print("What do you call a small mom? Minimum.")

class Son(Dad, Mom):
    pass
```
In this exemple the use of a method resolution *order* comes into play. Since 
multiple parent have a joke method to provide, and Son method resolution can 
only resolve to one of them, it is resolved to the first in order capable of 
delivering the method.
Here, ```Son().joke()``` resolves to the joke method of Dad.
Had Mom not have a joke method, nothing would change.
Had Dad not have a joke method, ```Son().joke()``` would have resolved to the 
joke method of Mom.
Had both Mom and Dad not have a joke method, an AttributeError would have been 
raised

Method resolution is also applied to class attributes. Essentially, a method 
bound class can be considered a callable attribute of this class, there's no 
specification of non callable attributes that would make them not need a method 
resolution algorithm.


I would argue that it is less than optimal that one parent method can be 
silently ignored by the method resolution on the pretext that another parent 
had a method with the same name "before". In some case, as the mixin case, such 
an order matches the need, but in the generic case, it is too speicifc of an 
assumption.

The ability for the child to resolve its parent method should be kept in, 
obviously (that's what inheritance is in the end)
However in case of multiple inheritance, and maybe more specifically, multiple 
resolution, a "manual" merge in the child class definition should be requested.
This goes for method and attributes, as of today, it is assumed one method is 
prefered, but other strategies might be the more viable ones. This assumption 
is something i disagree with. We should allow for multiple strategy, instead of 
enforcing one, and request a strategy when the default one can't provide a 
result.

The ability for the child to resolve automatically one of its parent method 
when only one parent can provide it is a viable default method, and should be 
kept as the default one, in this scenario.

Today's MRO doesn't allow for all possible class inheritance trees, as some 
can't be ordered, or would have inconsistent order.
such as :
```
class A: pass
class B(A): pass # B < A
class C(A,B): pass # A > B
```

TLDR:
 - keep:
  - resolution on class body method first
  - resolution implicit on parent method (if not present in own class body) 
when only one parent has it
 - don't keep
  - implicit resolution when multiple parent can resolve it
  - assumption of an order of parents. One might be more specialised than the 
other, but does not have to.


## Diamond problem

The diamond problem is essentially the question, should methods from a class 
which appears multiple time in an inheritance tree be called multiple time, or 
only once.
Essentially, multiple answers are possible, depending on your specific needs.
You might wanna want the bottom class inherit from the full behavior of all of 
it's parent classes, in which case, you'd call the grandparent method each time.
You might also want the grandparent class method to be called only once, after 
all other specialisation provided by all the parents.

Today, the only option provided by super + MRO is or the grandparent to be 
called only once.

I'd argue this is not enough.
We should be able to chose the strategy ourself, depending on our needs.

The diamond problem illustrate the problem when a class appears multiple time 
in an inheritance tree, but this shape doesn't have to be diamond.

For example:
```
class Top: pass

class Side(Top): pass

class Bottom(Side, Top): pass
```

TLDR:
allow for multiple inheritance strategies, instead of enforcing one.


## the weird case of __slots__
Someone mentionned __slots__

I didn't consider it at first, and now i'm weirded out by it.
(I'm running those exemples in python 3.6, according to more probing on my 
part, __slots__ behaves differently on python 2.7)

```
class A:
    __slots__ = ['a']
class B:
    __slots__ = ['b']
class C(B,A): pass
```
This code raises an error:
TypeError: multiple bases have instance lay-out conflict

Turns out, C doesn't like that A and B both define __slots__
So, my intuition was that redefining __slots__ in C would fix the issue
```
class A:
    __slots__ = ['a']
class B:
    __slots__ = ['b']
class C(B,A):
    __slots__ = ['c']
```
This raises the exact same error.

Multiple inheritance works for __slots__, at the strict condition that only one 
parent defines __slots__:
```
class A:
    pass
class B:
    __slots__ = ['b']
class C(B,A):
    pass
```
This doesn't raise an error.

Essentially, it seems the default method resolution, when applied to this 
__slots__ attribute wasn't satisfactory to whoever built it. They then decided 
to change the method resolution on this method to not allow implicit method 
resolution for multiple inheritance.
But, somehow, redefining __slots__ in the child isn't allowed either. Which 
should be an option to allows proper method resolution here.

In case of simple inheritance, more strange behavior occur.

```
class A: __slots__ = ['A']
class B(A): __slots__ = ['B']
```

This works, and now, B.__slots__ is equal to ['B']. Over riding works in this 
case.

I'm assuming this feature is still evolving, and testing in higher version of 
python would lead to different behaviors.

I feel the current features of method resolution wasn't satisfactory to 
__slots__, so they implemented their own strategy.


### My proposal

CONTEXT : I'll be exposing possible alternatives, but not go into much depths 
about the implementations.
I'll be proposing them in the order i think they should be introduced, so each 
new one can benefit from previous ones.


1) Alterhitance
CONTEXT: I'm assuming this proposal to be the first to be implemented.
No other proposal has to come first.
I will still try to pay attention to the final product.
However, this proposal is independant of any other coming proposal.
As such, it should be evaluated for the values it bring on its own first, and 
for the value it brings in the complete update (with all my other proposal) 
second.

This is the dedicated feature for dependency injection / inheritance tree 
alteration.
This could be a dedicated module, hosting the few utils functions allowing for 
easy access / use of the feature.

This one is fairly straightforward, the key idea is that i don't wanna rely on 
super or MRO, not so much at least.
It is possible to simply set the value of __bases__ of any class, so that would 
be my way to go.

In the same example as before :
```
class Glider:
    def push(self)
        print("I can't move so good on the ground")

    def jump(self):
        print("I'm free! I can fly now!")

class Wheel:
    def push(self)
        print("let's roll !")

    def jump(self):
        print("oh damn, i'm falling fast!")

class WheelGlider(Wheel, Glider):
    def push(self):
        super().push()
        super(Wheel, self).push()

    def jump(self):
        super(Wheel, self).jump()
        super().jump()
```

The way to inject the mocked class in would be:
```
from alterhitance import Alter

class MockedWheel(Wheel):
    def push(self):
        print("mocked wheel push")
    def jump(self):
        print("mocked wheel jump")

WithMockedWheel = Alter(WheelGlider).replace(parent=Wheel, 
new_parent=MockedWheel)

class MockedGlider(Glider):
    def push(self):
        print("mocked glider push")
    def jump(self):
        print("mocked glider jump")

WithMockedWheelAndGlider = Alter(WithMockedWheel).replace(parent=Glider, 
new_parent=MockedGlider)

class MockedWheelGlider(WithMockedWheelAndGlider):
d    def push(self):
        print("mocked WG push")
        super().push()
    def jump(self):
        print("mocked WG jump")
        super().jump()
```

This highlight the ability of this feature to produce each new class with a 
Mocked parent with relative ease.
Note that contrary to what we have to do now, MockedWheelGlider only needs to 
inherit from one class.

The way i've illustrated it here showcases that each class can be injected at 
once, without having to one shot everything.
If you were to create the mock of WheelGlider first, and wanted to mock one of 
it's parent down the inheritance tree, it could be doable like that:

```
from alterhitance import Alter

class MockedWheel(Wheel):
    def push(self):
        print("mocked wheel push")
    def jump(self):
        print("mocked wheel jump")

class MockedGlider(Glider):
    def push(self):
        print("mocked glider push")
    def jump(self):
        print("mocked glider jump")

class MockedWheelGlider(WheelGlider):
d    def push(self):
        print("mocked WG push")
        super().push()
    def jump(self):
        print("mocked WG jump")
        super().jump()

WithMockedWheel = Alter(MockedWheelGlider).replace(child=WheelGlider, 
parent=Wheel, new_parent=MockedWheel)
WithMockedWheelAndGlider = Alter(WithMockedWheel).replace(child=WheelGlider, 
parent=Glider, new_parent=MockedGlider)
```

Note that in this example, we not only define each mock in accordance *only* to 
what class they are mocking, but we still have a very simple interface for the 
bottom class MockedWheelGlider. It too only needs to inherit from WheelGlider.

Finally, having to call the replace method twice feels a bit redundant, maybe a 
syntax such as:
```
FullyMockedWheelGlider = Alter(MockedWheelGlider).replace(child=WheelGlider, 
remap={Wheel: MockedWheel, Glider: MockedGlider})
```
would be a nicer API.

What's to know :
Alter class from the alterhitance module take a class at initialisation.
all later operation it will run will happen on this class inheritance tree.

Assuming we're in the Alter(Example) instance:
the replace method takes a few arguments :
 - child, which is the class in the inheritance tree of Example (all occurence 
of this class, not only the first one, might need more argument to select one 
in particular if it is present multiple times today)
 - parent, which is supposed to be present in the __bases__ of child
 - new_parent, which is gonna take the place of parent in __bases__ of child 
(same index)
 - remap, which is essentially a dict of parent : new_parent.

I'm not sure if the argument child is needed, as finding out the child class in 
the inheritance tree is essentially the same problem as finding the parent 
class in the inheritance tree.
However, it could be a way to lock the remap only to parent in the __bases__ of 
the correct child class. This argument should probably be optional tho.


Alter could provide more methods, such as prune, and add_branch
We could also want to be able to select only a branch from the inheritance 
tree, which would help inspection of the class.
That could turn out usefull for dynamically generated classes, for example, 
classes altered by Alter itself.



Pros of this solution:
 - It makes it easier to mock classes individually as seen in the exemple. In 
general, it make dependency injection step by step possible.
 - The API as presented here allows to name each responsibility (Alter, 
replace, prune, add_branch) , which today's solution do not provide. This makes 
this feature much more accessible than today's very roundabout ways.
 - No knowledge of MRO or super is required.
 - it relies on __bases__, which is a little bit more straightforward (this is 
essentially the value that defines a class parents), and would make this 
feature more resilient to changes on MRO and super (no matter how MRO resolves 
anything, alterhitance updates the __bases__, so MRO will find the replacment 
at the same time it found the original).
 - does most definitely *not* need to change anything with current MRO + super 
to work.

Open questions:
 - I don't know exactly if we need to keep track of the remapping in 
inheritance trees that have been reworked. super's argument being classes, that 
we might be remapping or not, should super be changed to account for the new 
target? I think it works fine now, but in the long term goal of switching to a 
feature that would take the target of proxying as argument, that would turn out 
to be needed.
 - I'm not fixed on any method names, or the overall structure, with the Alter 
class, alterhitance module, and so on.
 - I'm not fixed either on methods signatures either
 - I'm not exactly sure how to cover remap of classes that appear multiple 
times in the inheritance tree. I'd want a way to target a specific appearance 
alone, this would allow to "undiamond" diamond cases. I also don't know how 
much this is needed or not.


2) Postponed / Delayed inheritance OR deep inheritance definition.
I'm not fixed on the name yet.

CONTEXT: I'm assuming this proposal comes in second.
I'm assuming the altheritance proposal was accepted, and implemented first.
I will still try to pay attention to the final product.
However, this proposal is independant of any other coming proposal.
As such, it should be evaluated for the values it bring on its own first, and 
for the value it brings in the complete update (with all my other proposal) 
second.

The example "Too many combinatory":
```
class View:
    def render(self):
        # do smthg
class ListView(View):
    def render(self):
        # do smthg
class DetailView(View):
    def render(self):
        # do smthg
...

class Permission:
    def render(self):
        # do smthg
        super().render()
        # do smthg
class LoginRequired:
    def render(self):
        # do smthg
        super().render()
        # do smthg
```

Showcases a usecase where we really want inheritance, but in practice, can't be 
reasonably expected.
The two reasons being:
 - there's way too many combinations
 - it's not really possible to reuse a child for another parent.

I propose that such scenarios would be able to define the inheritance links 
between the View classes and the mixins after their definition, when themselves 
inhreited from:

```
class MyView(
    LoginRequired(
        Permission(
            View
        )
    )
):
    pass
```

would be a valid syntax.

Essentially, the idea is that, at definition, the syntax ```Son(Dad)``` means 
Son inherits from Dad.
So, in the example above, it would mean :
MyView inherits from LoginRequired, which inherits from Permission, which 
inherits from View.


On its own, this syntax doesn't account well for cases where a mixin would be 
inheriting from multiple parents.
So it might be beneficial to add a Placeholder class like that :

```
class Permission(placeholder as view, placeholder as template):
    ...


class MyView(
    Permission(
        view = View,
        template = Template
    )
):
```

Thinking about the long term full proposal, having a way to tell which class 
replaces which without relying on the order of their declaration is relevant, 
as the proxy feature will likely have to account for remapping.


This feature could probably (partially at least) be provided by the 
alterhitance module.
```
Inherited = Alter(Permission).add_branch(View)
Inherited = Alter(LoginRequired).add_branch(Inherited)

class MyView(Inherited):
    ...
```

But this is not as nice an API as this proposal.

Pros:
 - it essentially extends the capacity of class definition.
 - it makes it very obvious which class specializes which, and produces the 
inheritances tree accordingly
 - it is consistent with the inheritance syntax we all know.
 - it isolates the feature from super + MRO more than today's solution, as the 
ability of super to target sideway, on the cases where we couldn't produce 
normal inheritance, is now covered
 - it replaces a multiple inheritance scenario with simple inheritance.



3) The Diamond problem
CONTEXT: I'm assuming this proposal comes in third.
I'm assuming the altheritance proposal was accepted, and implemented first.
I'm assuming the postponed inheritance proposal was accepted, and implemented 
second.
I will still try to pay attention to the final product.
However, this proposal is independant of any other coming proposal.
As such, it should be evaluated for the values it bring on its own first, and 
for the value it brings in the complete update (with all my other proposal) 
second.
Implementation specific details might require some more features tho, I'm still 
unclear on that.

As illustrated by the example "diamond tree, repeat top", it is possible to 
come up with scenarios in which the methods from the top part of the diamond 
are expected to be specialised by each parent extending it.
It is also very easy to come up with exemples where we would want to top parent 
specialised only once.
For exemple, if the top parent has a save method, that commits something to a 
database.
We wouldn't want one call to the bottom child save method to perform multiple 
commits

This highlights a need for multiple strategies.

I propose we add either a decorator, a class attribute, or maybe just an 
unbound method to specify this strategy.

```
from specialisation_strat import spec, strats
class Model:
    @spec(strats.after_last)
    def save(self):
        # call me once

class LeftModel(Model):
    def save(self):
        super().save()

class RightModel(Model):
    def save(self):
        super().save()

class BottomModel(LeftModel, RightModel):
    def save(self):
        super().save()
```

In this exemple, a call to BottomModel().save() visits Model save method only 
once, when the last call to super refering to it is performed.


```
from specialisation_strat import spec, strats
class HighGobelin:
    @spec(strats.after_each)
    def scream(self):
        print("raAaaaAar")

class CorruptedGobelin(HighGobelin):
    def scream(self):
        print("my corrupted soul makes me wanna scream")
        super().scream()

class ProudGobelin(HighGobelin):
    def scream(self):
        print("I ... can't ... contain my scream!")
        super().scream()

class HalfBreed(ProudGobelin, CorrupteGobelin):
    def scream(self):
        if random.choices([True, False]):
            super(HalfBreed, self).scream()
        else:
            super(ProudGobelin, self).scream()
```

In this exemple, a call to HalfBreed().scream() visits HighGobelin scream 
method every time a super().scream() call refering to it is performed.

We could also allow the child to decide how it wanna specialise it's 
inheritance tree, so instead of decorating the top parent, we would decorate 
the bottom method

Both option seem to make sense to me, i'd argue we could allow top parent 
definition *and* override from a child class.
For a child class to get more control, we could add another strategy 
after_classes that would expect a list of class.
this list of class would be the list of class whose call to super().method 
would not be ignored.

Whatever we decide, this would apply for cases more complex than the diamond 
cases, essentially, whenever a class appears multiple times in an inheritance 
tree.
I'm assuming more specific need could be covered, such as allowing strats per 
branch of the inheritance tree, but this might not be meaningful, as talking 
about branch doesn't make so much sense when they merge back together.


I think this could be implemented by making use of the altheritance module.
A strat that visits only after last call is the default option, but the strat 
to call it every time might need some magic from that.
Possibly by using a clone of the top class, (through something copy.deepcopy?) 
so that super and MRO don't identify it as the same class, and would therefore 
allow for multiple visits.

When the future proxy + method resolution features are implemented, 
altheritance would most definitely be the way to go i think.


Pros:
 - essentially solve the diamond problem
 - implementation specifics might rely on MRO and super, but this extract a 
behavior that we can easily evolve with those feature later on, to preserve the 
default behavior of today, and allow for more.

4) Proxy
CONTEXT: I'm assuming this proposal comes in fourth.
I'm assuming the altheritance proposal was accepted, and implemented first.
I'm assuming the postponed inheritance proposal was accepted, and implemented 
second.
I'm assuming the diamond problem proposal was accepted, and implemented third.
I will still try to pay attention to the final product.
However, this proposal is independant of any other coming proposal.
As such, it should be evaluated for the values it bring on its own first, and 
for the value it brings in the complete update (with all my other proposal) 
second.

Since we covered all features that relied on super relying on MRO, we can 
detach the proxy feature from the MRO feature.
This would be a breaking change, as after this change, all code not migrated to 
the 3 previous features would break.

Other than not relying on MRO, there's one thing i wanna change, the proxy 
feature argument should be the class it targets, instead of today's indirection 
where you have to pass as argument the previous in MRO order.

with the Halfbreed class in the previous example, it would change from
```
class HalfBreed(ProudGobelin, CorrupteGobelin):
    def scream(self):
        if random.choices([True, False]):
            super(HalfBreed, self).scream()
        else:
            super(ProudGobelin, self).scream()
```
to:
```
class HalfBreed(ProudGobelin, CorrupteGobelin):
    def scream(self):
        if random.choices([True, False]):
            self.__as_parent__(ProudGobelin).scream()
        else:
            self.__as_parent__(CorrupteGobelin).scream()
```

In case of simple inheritance, the parent wouldn't need to be passed to 
__as_parent__:

```
class Dad:
    def joke(self):
        print("I love your mom")

class Son(Dad):
    def joke(self):
        print("dad said")
        self.__as_parent__().joke()
```

Note that i'm not passing self as an argument to __as_parent__, i'm calling 
__as_parent__ on self instance instead.
This makes it awkward to call __as_parent__ for class attributes, like we 
'could' (the class wasn't available in its definition) with super:

```
class A:
    value = 10
class B(A):
    value = super().value # wouldn't actually work
```

would be replaced by
```
class A:
    value = 10
class B(A):
    value = __as_parent__().value # wouldn't actually work
```

However, direct A.value calls are enough, the proxy feature is not needed here 
at all
```
class A:
    value = 10
class B(A):
    value = A.value
```

We would need to adapt the diamond strats, as now passing the target class as 
argument, instead of implicitely selecting the next in MRO order switches the 
default behavior in diamond case from call after last, to call after each.
The default strat should be kept on call after last, to allow for smoother 
transition
The implementation of the diamond strat module would have to adapt to the 
change of targeting strategy of the proxy feature, which might not be automatic

The altheritance module should not require a lot of extra changes.
Maybe, thinking of a class to add for pruned branches would be nice, as it 
would allow the childs to proxy it, instead of crashing for the lack of a 
target.
A remap dict should probably be stored in classes that are remapped, so the 
proxy feature can reroute the calls from the target to the remapped parent.

The postpone inheritance feature would probably benefit from the introduction, 
if not done already, of the placeholder parent, so they could serve as target 
of the proxy (which has to be explicit calls in case of multiple inheritance).

Pros:
 - implicitness of the target is still possible
 - explicitness of the target is required when multiple parent are present 
(easier to read / understand the code later)
 - same simplicity of API.

5) Method resolution
CONTEXT: I'm assuming this proposal comes in last.
I'm assuming the altheritance proposal was accepted, and implemented first.
I'm assuming the postponed inheritance proposal was accepted, and implemented 
second.
I'm assuming the diamond problem proposal was accepted, and implemented third.
I'm assuming the proxy proposal was accepted, and implemented fourth, with all 
the update to the previous 3 features to survive this breaking change.
I'm talking here only about the changes to the method resolution, and 
eventually how it integrates with previous faetures, but not about the previous 
features themselves. 
As such, it should be evaluated for the values it bring on its own first, and 
for the value it brings in the complete update (with all my other proposal) 
second.


Today's MRO forces resolution in any scenarios, which comes at the cost of not 
allowing some inheritance trees.

My proposal is that we stop ordering the parents.

The specs are:
a method defined in a class body should always be the one resolved to.
when only one parent can resolve it, resolves to this method.
when no parent can resolve it, raises an AttributeError
when multiple parent can resolve it, raises an ExplicitResolutionRequired


to give some examples :
straightforward
```
class A:
    def method(self): # A.method resolves to this one
        pass
```

one parent:
```
class P1: pass
class P2: pass
class P:
    def method(self): # A.method resolves to this one
        pass
class A(P1, P2, P): pass
```

multiple parents:
```
class P1:
    def method(self): # this is a candidate for A.method
        pass
class P2:
    def method(self): # this is a candidate for A.method
        pass
class A(P1, P2): pass # A.method have multiple candidate, it raises (at 
runtime) an ExplicitnessRequiredError
```

how to 'survive' an expliciteness required error:
```
class P1:
    def method(self): # this is not a candidate for A.method anymore
        pass
class P2:
    def method(self): # this is not a candidate for A.method anymore
        pass
class A(P1, P2):
    def method(self): # this is what A.method resolves to
        pass # eventual calls to self.__as_parent__(P1).method() and 
self.__as_parent__(P2).method() here
```

Pros:
 - It requires explicitness when it can't resolve implicitely
 - It works with any inheritance tree (even circular ones if we had some code 
to prevent loops...)
 - You won't end up accidentally losing one method / attribute from one parent 
only because another one had it first
_______________________________________________
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/JKF4Q5U4VVB2QHI2QN6HHW6JQIXUPQDE/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to