Repository navigation
Add typing.get_orig_bases and typing.get_orig_class #101688
Description
Activity
- addedtype-featureA feature request or enhancementA feature request or enhancement
on Feb 8, 2023 I'd be happy to accept this enhancement to
typing.py. Users have previously requested this functionality in microsoft/pyright#3442 and python/typeshed#7811, but it's not really fixable by pyright or typeshed. The solution is to add the APIs suggested here totyping.pyin CPython.- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory3.12only security fixesonly security fixes
on Feb 8, 2023 Aren't
__orig_bases__and__orig_class__enough?They aren't documented anywhere AFAIK, that would also be a decent solution to the problem
Aren't
__orig_bases__and__orig_class__enough?No — type checkers emit errors if you try to access them, as they're not included in the stub. As discussed in python/typeshed#7811 and python/typeshed#7827, adding them to the stub would in fact be impossible.
The main issue with defining those dunders in a stub file is they are defined for generic types but not other types. And Generic class is very special cased by type checkers/runtime that you can’t add it to the stub definition for Generic without risking type checker either ignoring it or causing buggy behavior.
Would be nice to have a public recommended way to access these for runtime type reflection. Simple implementation seems fine to me.
Reacted by Alex Waygood and GalaxySnail- added a commit that references this issue
on Apr 23, 2023 #101827 has now been merged:
types.get_original_baseswill be available for retrieving a class's__orig_bases__attribute in 3.12+.Remaining issues to discuss
(1) Should we add a "getter function" for the
__orig_class__attribute? If not, should we document its existence?As a refresher, these are the circumstances in which an object gets an
__orig_class__attribute:>>> import typing >>> T = typing.TypeVar("T") >>> class Foo(typing.Generic[T]): ... ... >>> f = Foo[int]() >>> f.__orig_class__ __main__.Foo[int] >>> m = typing.ChainMap[int, str]() >>> m.__orig_class__ typing.ChainMap[int, str] >>> import collections >>> m2 = collections.ChainMap[int, str]() >>> m2.__orig_class__ collections.ChainMap[int, str]
However, it only exists on instances of classes that don't have slots.
>>> d1 = dict[int, str]() >>> d1.__orig_class__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'dict' object has no attribute '__orig_class__'
The inconsistency in whether this attribute is available or not might be quite confusing for users, so I'm not sure I like the idea of documenting the attribute or adding a getter function. It also doesn't appear to have been as much of a pain point for users as
__orig_bases__has been. @Gobot1234, could you elaborate more on your use case for accessing the__orig_class__attribute?(2) Should we document
__orig_bases__in the data model? Or is havingtypes.get_original_bases()enough?We could possibly document the attribute formally in the data model here: https://docs.python.org/3.12/reference/datamodel.html#resolving-mro-entries. However, doing so would be equivalent to a formal promise that the attribute will always exist as part of the language. I'm not sure I'm convinced that it's necessary for us to do that. PEP 560 describes the attribute as an implementation detail of the typing module, and it is not currently formally documented anywhere:
If an object that is not a class object appears in the tuple of bases of a class definition, then method
__mro_entries__is searched on it [...] The original bases are stored as orig_bases in the class namespace [...] NOTE: These two method names are reserved for use by the typing module and the generic types machinery, and any other use is discouraged.So for now I think documenting
__orig_class__isn't probably worth it as as you pointed out, it's only conditionally available (I'm currently drafting up a PEP for better runtime type checking features and it will probably become public API following that if it's accepted or submitted ;)) but I currently use it for getting the specialised value of a type parameter at runtime.Currently in a module I maintain there is an
Inventoryclass which is generic over the types of items it stores and I want to get that at runtime so that I can instantiate the correct item class.
This either goes through subscription e.g.Inventory[SomeItemClass]()orclass InventorySubclass(Inventory[AlwaysTheSameItem]): ...the first of which requires checking__orig_class__the other runs through__orig_bases__hence why I made this issue (full code here https://github.com/Gobot1234/steam.py/blob/82e74825d55db905dcb7414fa3f9313b67e31aba/steam/trade.py#L337-L340 if you're interested)Okay, great. I'll close this as completed for now, in that case, as I think adding a new slot for all Python objects will require a lot more discussion, so deserves to be treated separately :)
Reacted by James Hilton-Balfe
Feature or enhancement
typing has had
type.__orig_bases__andtype.__orig_class__for quite some time now, there is no stable API to access these attributes.Pitch
I would like to propose adding
typing.get_orig_basesas something likeand
typing.get_orig_class(side note, it might be possible to fully type
get_orig_classittypes.GenericAliaswas generic over the__origin__and__args__i.e.Foo[int] == GenericAlias[Foo, int])Linked PRs