typing_inspect
typing_inspect copied to clipboard
get_args
Is it possible to use typing_inspect to achieve the following?
from typing import Any, List, TypeVar
import typing_inspect
class C:
x: int
def __init__(self, x: int) -> None:
self.x = x
T = TypeVar("T")
class L(List[T]):
def append_new(self, *args: Any, **kw: Any) -> T:
item_class = ???(T)
self.append(item_class(*args, **kw))
return self[-1]
l = L[C]()
print(l.append_new(42).x)
T doesn't hold any of the cool information you think it does, it's L's instance that does. You may want to have a look at the alternates to get_args and it's arguments, but the following works in Python 3.7.2.
item_class = typing_inspect.get_args(typing_inspect.get_generic_type(self))[0]
@Peilonrayz this works, both on 3.7.3 and 3.6.8 (which include "new" and "old" versions of typing).
I am aware that T doesn't hold any cool information but is merely a token. However, I imagine it should be possible to use it instead of an explicit index ([0]).
So, I would expect to have something like typing_inspect.get_xxx (cannot find a good name for it), that would work in the following scenario:
from typing import List, Tuple, TypeVar
import typing_inspect
U = TypeVar("U")
V = TypeVar("V")
W = TypeVar("W")
Z = 1
class L(List[Tuple[U, V]]):
def __init__(self) -> None:
self._type_U = typing_inspect.get_xxx(self, U) # int
self._type_V = typing_inspect.get_xxx(self, V) # str
self._type_W = typing_inspect.get_xxx(self, W) # exception: "type W is not among generic parameters"
self._type_Z = typing_inspect.get_xxx(self, Z) # exception: "1 is not a type"
l = L[int, str]()
l.f()
Another interesting alternative I see would be a decorator that injects all types as instance variables:
from typing import List, Tuple, TypeVar
import typing_inspect
U = TypeVar("UU")
V = TypeVar("VV")
@typing_inspect.inject_types_or_some_better_name
class L(List[Tuple[U, V]]):
pass
l = L[int, str]()
assert l._type_UU == int
assert l._type_VV == str
It doesn't look like this is at all possible to do in the constructor, in Python 3.7.2 at least.
This is as typing_inspect.get_generic_type(self) returns <class '__main__.L'> not __main__.L[int, str].
Otherwise it looks rather simple:
from typing import List, Tuple, TypeVar
import typing_inspect
U = TypeVar("U")
V = TypeVar("V")
W = TypeVar("W")
Z = 1
def get_type_argument(instance, type_var):
generic = typing_inspect.get_generic_type(instance)
origin = typing_inspect.get_origin(generic)
params = typing_inspect.get_parameters(origin)
index = params.index(type_var)
return typing_inspect.get_args(generic)[index]
class L(List[Tuple[U, V]]):
pass
l = L[int, str]()
print(get_type_argument(l, U))
print(get_type_argument(l, V))
try:
print(get_type_argument(l, W))
except ValueError as e:
print(e)
try:
print(get_type_argument(l, Z))
except ValueError as e:
print(e)
So, do you think something like that should be included as a part of typing_inspect and not be left as "an exercise for the reader"?
On one hand, there is a plan to add some extra functionality to typing_inspect (see #31), I could imagine some kind of helper like this can be added there. On the other hand get_generic_type(self) returning the plain class needs to be fixed somehow.
Maybe pytypes.get_arg_for_TypeVar(typevar, generic) is what you are looking for.
From my experience, pytypes still lacks proper python 3.7 support.
On Fri, 3 May 2019, 09:40 Stefan Richthofer, [email protected] wrote:
Maybe pytypes.get_arg_for_TypeVar(typevar, generic) https://github.com/Stewori/pytypes#get_arg_for_typevartypevar-generic is what you are looking for.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/ilevkivskyi/typing_inspect/issues/35#issuecomment-488965943, or mute the thread https://github.com/notifications/unsubscribe-auth/AAOCW3S52ZKUQTNG72CAWUTPTPM5JANCNFSM4HJWBBZA .
Feel free to make a PR for that ;) (Edit: I really hope to be able to provide Python 3.7 support for pytypes. Still I found it important to cross-link this feature here already now. In the hope that it can be useful.)
Regretfully, solution from https://github.com/ilevkivskyi/typing_inspect/issues/35#issuecomment-488386120 fails in case of inheritance:
from typing import List, TypeVar
import typing_inspect
T = TypeVar("T")
class L(List[T]):
def f(self) -> None:
print(typing_inspect.get_args(typing_inspect.get_generic_type(self)))
class L2(L[int]):
pass
L[int]().f()
L2().f()
prints
(<class 'int'>,)
()
Digging into typing.py it seems to make sense why this happens: _GenericAlias.__call__ is the place where __orig_class__ is set. Which also explains why this does not work in constructor. But does it mean there is no elegant solution? The only practical one I can think of, which isn't anywhere close to elegant to my taste is something like:
from typing import List, TypeVar
import typing_inspect
T = TypeVar("T")
class L(List[T]):
def f(self) -> None:
types = getattr(self, "_L_types", None)
if types is None:
types = typing_inspect.get_args(typing_inspect.get_generic_type(self))
print(types)
class L2(L[int]):
_L_types = (int, )
L[int]().f()
L2().f()
This is as the arguments are on the base class. Using get_generic_bases works fine.
from typing import List, TypeVar
import typing_inspect
T = TypeVar("T")
class L(List[T]):
def f(self) -> None:
types = typing_inspect.get_args(typing_inspect.get_generic_type(self))
if not types:
types = typing_inspect.get_args(typing_inspect.get_generic_bases(self)[0])
print(types)
class L2(L[int]):
pass
L().f()
L[int]().f()
L2().f()
It should be noted that this is hacky solution, and may give unexpected results if the class is class C(Mapping[int, T], Sequence[str]). The behavior can be seen in one of typing_inspect_lib.get_mro_orig tests. This function isn't available until #31 has been completed.
Using this should be fairly simple when it does come out: (Note: untested and subject to change)
for type_info in get_mro_orig(L2()):
if type_info.typing is L:
print(get_args(type_info.orig))
It should be noted that this won't return (int, T, str) if used on C, in an equivalent way. It however is rather simple to change it to return this, return a combination of Mapping and Sequence.
For the record. In a recent commit I fixed workability of pytypes.get_Generic_parameters for Python 3.7 (however, not released). It can now be used like this:
T = TypeVar("T")
class L(List[T]):
def f(self) -> None:
print(pytypes.get_Generic_parameters(pytypes.get_Generic_type(self)))
class L2(L[int]):
pass
L[int]().f()
L2().f()
I know, the name is misleading as it retrieves args, not parameters. That came because it is originally intended to provide control over the parameters to select: the second, optional argument can be some base, e.g. Container or Mapping then it retrieves only the args corresponding to the parameters specified by this base.