Assume you have a Python class that inherits from Generic[T]. Is there any way to get a hold of the actual type passed in from within the class/instance?
For example,
from typing import TypeVar, Type
T = TypeVar('T')
class Test(Generic[T]):
def hello(self):
my_type = T # this is wrong!
print( "I am {0}".format(my_type) )
Test[int]().hello() # should print "I am int"
On here, it is suggested the type arg would be present in in the args field of the type. And indeed,
print( str( Test[int].__args__ ) )
would print (<class 'int'>,). However, I can't seem to access this from within the instance directly, e.g. substituting
my_type = self.__class__.__args__ # this is also wrong (None)
doesn't seem to to the trick.
Thanks
There is no supported API for this. Under limited circumstances, if you're willing to mess around with undocumented implementation details, you can sometimes do it, but it's not reliable at all.
First, mypy doesn't require you to provide type arguments when assigning to a generically-typed variable. You can do things like x: Test[int] = Test()
and neither Python nor mypy will complain. mypy infers the type arguments, but Test
is used at runtime instead of Test[int]
. Since explicit type arguments are awkward to write and carry a performance penalty, lots of code only uses type arguments in the annotations, not at runtime.
There's no way to recover type arguments at runtime that were never provided at runtime.
When type arguments are provided at runtime, the implementation does currently try to preserve this information, but only in a completely undocumented internal attribute that is subject to change without notice, and even this attribute might not be present. Specifically, when you call
Test[int]()
, the class of the new object is Test
rather than Test[int]
, but the typing
implementation attempts to set
obj.__orig_class__ = Test[int]
on the new object. If it cannot set __orig_class__
(for example, if Test
uses __slots__
), then it catches the AttributeError and gives up.
__orig_class__
was introduced in Python 3.5.3; it is not present on 3.5.2 and lower. Nothing in typing
makes any actual use of __orig_class__
.
The timing of the __orig_class__
assignment varies by Python version, but currently, it's set after normal object construction has already finished. You will not be able to inspect __orig_class__
during __init__
or __new__
.
These implementation details are current as of CPython 3.8.2.
__orig_class__
is an implementation detail, but at least on Python 3.8, you don't have to access any additional implementation details to get the type arguments. Python 3.8 introduced typing.get_args
, which returns a tuple of the type arguments of a typing
type, or ()
for an invalid argument. (Yes, there was really no public API for that all the way from Python 3.5 until 3.8.)
For example,
typing.get_args(Test[int]().__orig_class__) == (int,)
If __orig_class__
is present and you're willing to access it, then __orig_class__
and get_args
together provide what you're looking for.
you can use self.__orig_class__
:
from typing import TypeVar, Type, Generic
T = TypeVar('T')
class Test(Generic[T]):
def hello(self):
print( "I am {0}".format(self.__orig_class__.__args__[0].__name__))
Test[int]().hello()
# I am int
Currently (Python 3.10.3) one cannot access the type parameter during __init__
or __new__
.
However, it's possible to access the type variable in __init_subclass__
. It's a bit different scenario, but I think it's interesting enough to share.
from typing import Any, Generic, TypeVar, get_args
T = TypeVar("T")
class MyGenericClass(Generic[T]):
_type_T: Any
def __init_subclass__(cls) -> None:
cls._type_T = get_args(cls.__orig_bases__[0])[0] # type: ignore
class SomeBaseClass(MyGenericClass[int]):
def __init__(self) -> None:
print(self._type_T)
SomeBaseClass() # prints "<class 'int'>"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With