-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Figure out story about diamond inheritance #1065
Comments
I haven't looked at the code, but why is this not using Python's standard MRO algorithm? In Python 3 that's always used and in Python 2 it's used for new-style classes (those deriving from |
I think of the MRO algorithm as providing an answer to the question "which of my ancestors do I get method Does the MRO algorithm also provide an answer to the question "which path of intermediate ancestors do I get to ancestor |
Ah, it doesn't directly answer that -- in your diamond example D's MRO is
[D, B, C, A] but the paths from D to A are D-B-A and D-C-A; those are both
sub-sequences of the MRO (as all valid paths should be), but not every
sub-sequence is such a path.
As I said, I didn't look at the code -- what is this path (or list of
paths) used for?
|
This is my favorite (assuming we tweak this to also consider
However, initially just not doing anything about it is probably okay, as this is not very likely to cause much trouble. |
Can someone explain this to me? What is the need for this rule, and what
does it even mean -- what kind of "instances" are we talking about here?
|
The writeup uses internal mypy terminology which makes it difficult to follow. Here is an example about where this would make a difference:
The rule I quoted would disallow inheritance hierarchies such as the above because the different ways of getting from D to A would result in different base types ( This would be okay, though:
|
Ah, I see. OTOH inheriting from two different parameterized generic classes
Also, I'm still unsure why the code needs to look for paths rather than |
Yeah, your example would be no problem because Paths are needed because you can have some really weird, complex inheritance hierarchies. Here is an example:
Now we'd look at the paths D -> CC -> C -> A and D -> B -> A to determine if things are compatible. |
Actually Python itself (actually typing.py in the stdlib) doesn't currently
Strangely, mypy gives an error on C:
The source:
This is an area where mypy, PEP 484 and typing.py all seem to have |
Mypy wants you to do it like this (because of #302):
The code is still reasonable from a type checking point of view, other than having the incoherent generic base class problem. Maybe typing.py should not reject it? |
Yeah, there's something fishy with typing.py here. :-( I think there's even
an issue about it in https://github.com/ambv/typehinting/issues but I've
lost track of which one it is.
|
Yeah, I think this one is probably the right semantics in the end. It's just a little complicated to implement. Either this or either of the simpler options mean that the I'd rather do a little more than make no change to the code -- I'd like to bring the comments in line with the code's behavior one way or another. The simple way would be to do, say, my second option for now:
enforcing it somewhere like |
Hmm. I implemented that simple second option:
Pushed it as master...gnprice:diamond for reference. But it turns out we actually already use diamond inheritance with generics! The unit tests tell me this -- I produce errors in The latter two seem to be down to a mismatch with the The other one is more real, though:
while versions of both So maybe we actually do need option 3:
Or can we do without some of this hierarchy over |
(Fixing that mismatch in python/typeshed#32 , but that still leaves |
In
mypy/maptype.py
, we have a functionclass_derivation_paths
that finds all the paths up the hierarchy from a derived class to a given ancestor class. The code implicitly assumes that this function will only ever return a single path, for example by silently taking the first element of a list derived from its result and dropping the rest:And indeed there are a couple of assertions in comments in the code that this function should only ever find a single path:
But this doesn't seem to be true! In fact, if we set up the classic diamond inheritance pattern:
then we do in fact get two paths, one through
B
and one throughC
. Demonstrated by adding an assert and a triggering test case here: gnprice@a85876521which produces this exception:
What should our approach to this situation be?
In roughly increasing order of semantic and implementation complexity, some options include
I'm inclined to start with option 1, as it's super simple to understand and to implement, and see if that ever poses a problem. Maybe nobody actually uses diamond inheritance in code they want to try to type; it's a pretty tricky pattern in the first place. If we want to do this, we should detect the situation up front (like in
verify_base_classes
in semanal.py) and give an appropriate error.Option 2 is also super simple to implement, if people use diamond inheritance but they don't need generics with it. Somewhat less satisfying as a rule for the user.
I'd be kind of concerned if we find ourselves starting to think we need one of the more complex options.
The text was updated successfully, but these errors were encountered: