Getting type of a 'concrete generic' inside another generic, at runtime

In this example is there any way I can get int out at runtime (instead of ~T),
or is that information gone?

from types import get_original_bases
from typing import Generic, TypeVar

T = TypeVar('T')

class GenericThing(Generic[T]):
    t: T

class SpecificThing(GenericThing[int]):
    t: int

class GenericJob(Generic[T]):
    t_thing: GenericThing[T]

    def __init__(self, t_thing: GenericThing[T]) -> None:
        self.t_thing = t_thing

job = GenericJob(SpecificThing())
job_type = type(job)
print(get_original_bases(job_type))  # (typing.Generic[~T],)

You can’t really rely on inference in runtime code, so the only ways to ensure that you can access the type at runtime is either to be explicit or add additional code to the constructor to do this inference for you, so you can access the type later on.

But in this case, even when you do that you can’t currently really do it using the usual introspection machinery because GenericThing[int] is a typing._GenericAlias at runtime which is not a valid type, so it can’t be assigned to __class__ or be part of __mro__, so it has to live inside a separate attribute named __orig_class__[1].

This is a real limitation of using the Python type system at runtime and it often forces you to do things like passing a type as a regular parameter, rather than a type parameter, if you want to take advantage of the type at runtime while retaining some generic behavior. You can have a look at libraries like pydantic and cattrs to see how you might achieve this.

There have been some proposals to improve the runtime behavior of generics in the past, in order to make them easier to use, but none have reached the PEP stage yet, partially because PEP 718 hasn’t been accepted yet, which would be necessary to also get the same benefit with generic functions, not just classes.

In this case you can do:

job = GenericJob[int](SpecificThing())
job_type = job.__orig_class__
print(typing.get_args(job_type))  # (<class 'int'>,)

  1. this access has to happen on the instance and not the class, because there’s only one instance of the class, so it can’t store the information for a specific variant, only individual instances can ↩︎

1 Like

I feared as much, thanks for the great response