Bug report
Checklist
A clear and concise description of the bug
(I originally posted about this on python-list a little over a week ago, but didn't get any replies.) I was playing around with 3.12.0b4 recently and noticed an odd (to me, at least) behavior with types.get_original_bases().
>>> T = typing.TypeVar("T")
>>> class FirstBase(typing.Generic[T]):
... pass
...
>>> class SecondBase(typing.Generic[T]):
... pass
...
>>> class First(FirstBase[int]):
... pass
...
>>> class Second(SecondBase[int]):
... pass
...
>>> class Example(First, Second):
... pass
...
>>> types.get_original_bases(Example)
(__main__.FirstBase[int],)
>>> Example.__bases__
(<class '__main__.First'>, <class '__main__.Second'>)
>>> types.get_original_bases(First)
(__main__.FirstBase[int],)
In summary, types.get_original_bases(Example) is returning the original base types for First, rather than its own.
I believe this happens because __orig_bases__ is only set when one or more of a generic type's bases are not types. In this case both bases are types, so Example doesn't get its own __orig_bases__. Then when types.get_original_bases() tries to get __orig_bases__ on Example, it searches the MRO and finds __orig_bases__ on First.
The same thing also happens if all the bases are “bare” generic types.
>>> class First(typing.Generic[T]):
... pass
...
>>> class Second(typing.Generic[T]):
... pass
...
>>> class Example(First, Second):
... pass
...
>>> types.get_original_bases(Example)
(typing.Generic[~T],)
I'm not entirely clear if this is a bug, or an intended (but unfortunate) behavior. I would personally expect types.get_original_bases() to check if the type has its own __orig_bases__ attribute, and to fall back to __bases__ otherwise.
For example, the way it works currently makes it unnecessarily difficult to write a function that recurses down a type's inheritance tree inspecting the original bases—I currently have to work around this behavior via hacks like checking "__orig_bases__" in cls.__dict__ or any(types.get_original_bases(cls) == types.get_original_bases(base) for base in cls.__bases__). (Unless I'm missing some simpler solution.)
Is this something that could (should?) be addressed before 3.12 lands?
Your environment
% docker run -it --rm python:3.12-rc
Python 3.12.0b4 (main, Jul 28 2023, 03:58:56) [GCC 12.2.0] on linux
Linked PRs
Bug report
Checklist
A clear and concise description of the bug
(I originally posted about this on python-list a little over a week ago, but didn't get any replies.) I was playing around with 3.12.0b4 recently and noticed an odd (to me, at least) behavior with
types.get_original_bases().In summary,
types.get_original_bases(Example)is returning the original base types forFirst, rather than its own.I believe this happens because
__orig_bases__is only set when one or more of a generic type's bases are not types. In this case both bases are types, soExampledoesn't get its own__orig_bases__. Then whentypes.get_original_bases()tries to get__orig_bases__onExample, it searches the MRO and finds__orig_bases__onFirst.The same thing also happens if all the bases are “bare” generic types.
I'm not entirely clear if this is a bug, or an intended (but unfortunate) behavior. I would personally expect
types.get_original_bases()to check if the type has its own__orig_bases__attribute, and to fall back to__bases__otherwise.For example, the way it works currently makes it unnecessarily difficult to write a function that recurses down a type's inheritance tree inspecting the original bases—I currently have to work around this behavior via hacks like checking
"__orig_bases__" in cls.__dict__orany(types.get_original_bases(cls) == types.get_original_bases(base) for base in cls.__bases__). (Unless I'm missing some simpler solution.)Is this something that could (should?) be addressed before 3.12 lands?
Your environment
Linked PRs
__orig_bases__are our own inget_original_bases#107584__orig_bases__are our own inget_original_bases(GH-107584) #107592