-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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
cannot create consistent MRO for multiple generic inheritance #106102
Comments
Haven't had time to look into this yet, but generally |
That would've been my alternative suggestion, and I do still think it would be a nice improvement and a satisfcatory resolution. But the lines I linked gave me the impression that it should in fact bepossible to use it anywhere. |
@JelleZijlstra have you had any chance to give this some thought? This behavior has been a bit of a nuisance to us: we added a generic base to one of our library's classes, which broke some usages of the form It would be wonderful if this just worked, as with my patch, but failing that it would be a great help if we could expect library clients to put |
I played with your proposed patch for a bit and couldn't find anything obviously broken by it, but I don't understand that code well enough to be confident the change is correct. I would prefer documenting that |
Thanks for your input. Do you know of anyone who might be more acquainted with this code? Because I do still feel like my patch would be consistent with what is already there, and therefore the current behavior could be considered a (albeit minor) bug. Then again, I can't claim to fully understand all nuances of this code myself. If not, I'll see if I can open a small documentation PR with the requirement to put |
I've seen lots of real life uses of
Moreover, in some cases different >>> class L1(Generic[T], list[T]): ...
...
>>> class L2(list[T], Generic[T]): ...
...
>>> L1.__mro__
(<class '__main__.L1'>, <class 'typing.Generic'>, <class 'list'>, <class 'object'>)
>>> L2.__mro__
(<class '__main__.L2'>, <class 'list'>, <class 'typing.Generic'>, <class 'object'>)
>>> type(L1[int])
<class 'typing._GenericAlias'>
>>> type(L2[int])
<class 'types.GenericAlias'> |
typing.Generic is a magical class that can be specified in any position in the list of base classes, not affecting the MRO consistency. It's done by the custom __mro_entries__ implementation in typing._BaseGenericAlias (Python < 3.12), which skips this Generic entry if there are other generic classes following it on the list of superclasses. Namely, it's possible to do the following: ``` class Base(Generic[T]): pass class MyClass(Generic[T], Base[T]): pass ``` which would cause a TypeError for regular classes. Since it broke our implementation of the C3 algorithm in PyClassImpl.getMROAncestorTypes, we now special-case it by always moving typing.Generic to the very end of the base class list while constructing MRO. See https://github.com/python/cpython/blob/3.11/Lib/typing.py#L1298 for a pure-Python version of typing._BaseGenericAlias.__mro_entries__ and a relevant discussion in python/cpython#106102. GitOrigin-RevId: e7d765193d532ab8457133e8fb5ad06840d89225
Bug report
When
Generic
appears twice in a class' inheritance tree, Python may fail to find a consistent MRO. An effort seems to be taken to address this in typing._BaseGenericAlias and typing._GenericAlias. However, when one of the bases is a subscripted generic and the other is a child of a subscripted generic (no longer generic itself) this falls short. I believe the root cause is the fact that_GenericAlias
, unlike its parent, only checks forisinstance(b, _BaseGenericAlias
and not forissubclass(b, Generic)
when considering whether it should skip theGeneric
base for this MRO entry.Consider the following example:
The
Works
class does not present a problem becauseGeneric[T]
appears as the last base. TheWorksToo
class works becauseA[int]
is recognized by_GenericAlias.__mro_entries__
as another_BaseGenericAlias
, thereforeGeneric
is only included as MRO entry for the latter.Broken
results in an exception, even thoughB
is semantically equivalent toA[int]
.I managed to get it to run correctly by making the following change to
typing.py
:I am not sufficiently familiar with typing's internals (or even MRO) to be completely confident of this patch, but I believe it to be sound. If this issue gets confirmed I'd be willing to open a pull request with this change.
Your environment
Linked PRs
The text was updated successfully, but these errors were encountered: