Skip to content
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

Odd types.get_original_bases() behavior for classes with generic bases but no type arguments #107576

Closed
2 tasks done
chrisbouchard opened this issue Aug 3, 2023 · 6 comments
Closed
2 tasks done
Labels
release-blocker topic-typing type-bug An unexpected behavior, bug, or error

Comments

@chrisbouchard
Copy link
Contributor

chrisbouchard commented Aug 3, 2023

Bug report

Checklist

  • I am confident this is a bug in CPython, not a bug in a third-party project
  • I have searched the CPython issue tracker, and am confident this bug has not been reported before

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

@chrisbouchard chrisbouchard added the type-bug An unexpected behavior, bug, or error label Aug 3, 2023
@JelleZijlstra
Copy link
Member

Agree that this is unfortunate and we should fix it, preferably before 3.12.0 final, as this is a new function. cc @Yhg1s and @Gobot1234

@Gobot1234
Copy link
Contributor

The proposed fix of checking cls.__dict__ seems to work, happy to PR a fix.

@Yhg1s
Copy link
Member

Yhg1s commented Aug 3, 2023

RC1 is still pending another fix, so if we can get a simple fix for this in quickly, that would be good :) (The 3.12 ranch is locked to anyone but me, so send PRs my way for the backport merge).

@AlexWaygood
Copy link
Member

@chrisbouchard, thanks for testing with the betas! It's really helpful to get these bug reports early :D

@chrisbouchard
Copy link
Contributor Author

@AlexWaygood I was very excited to try out types.get_original_bases(). I've been working on some reflective code, and this will definitely simplify things.

Thank you all for being so responsive! This was really awesome 👍

AlexWaygood added a commit that referenced this issue Aug 3, 2023
#107584)

Co-authored-by: Chris Bouchard <chris@upliftinglemma.net>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Aug 3, 2023
…_bases` (pythonGH-107584)

(cherry picked from commit ed4a978)

Co-authored-by: James Hilton-Balfe <gobot1234yt@gmail.com>
Co-authored-by: Chris Bouchard <chris@upliftinglemma.net>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Yhg1s pushed a commit that referenced this issue Aug 3, 2023
…l_bases` (GH-107584) (#107592)

gh-107576: Ensure `__orig_bases__` are our own in `get_original_bases` (GH-107584)
(cherry picked from commit ed4a978)

Co-authored-by: James Hilton-Balfe <gobot1234yt@gmail.com>
Co-authored-by: Chris Bouchard <chris@upliftinglemma.net>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
@AlexWaygood
Copy link
Member

AlexWaygood commented Aug 3, 2023

Fixed and backported; thanks again everybody!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-blocker topic-typing type-bug An unexpected behavior, bug, or error
Projects
Development

No branches or pull requests

5 participants