Repository navigation
Allow type aliases such X = List[T] if T is a type variable #606
Description
Activity
This is not included in PEP 484, so closing this.
Whoa, maybe this should be added to PEP 484? I still am in favor of this.
In particular, PEP 484 (https://github.com/ambv/typehinting/blob/master/pep-0484.txt) contains this example:
Vector = Iterable[Tuple[T, T]]What about something like this:
Table = Dict[str, T] def f(t: Table[int]) -> ...: ...The implementation in typing.py requires that you write
Table[str, int]in the argument attribute. I agree this is a bit arbitrary, but the alternative felt pretty confusing. In an earlier version I was trying to support this to the extreme, allowing e.g.# T, U, V are TypeVars A = Tuple[T, U, V] X = Dict[int, Union[Tuple[U, U], A]]and then X would have 3 parameters (U, T, V). That felt too complicated, so I retreated on the rule that you can't change the number of parameters at all, and substitutions must be either completely concrete or single type variables, and the resulting type still has the original number of parameters. Maybe we need to find a middle ground, where your example is allowed and does in fact reduce the number of parameters. But you could still have only substitutions that are either completely concrete or a single type variable. An edge case would be
X = Dict[T, T]which gives X a single parameter, T.Just stumbled upon this, the confusing thing is that the error is reported not at the line of the type alias, but at the line at which the TypeVar (
Tin this case) has been defined:error: Invalid type "module.T"Reacted by Dashvitawasalreadytaken commented
on Sep 8, 2016 ContributorMore actionsWith mypy 0.4.4 I'm getting the error at the line of the type alias:
from typing import TypeVar, Tuple T = TypeVar('T') U = TypeVar('U') X = Tuple[T, U]
$ mypy test.py test.py:6: error: Invalid type "test.T" test.py:6: error: Invalid type "test.U"+1 for this
I'm developing a dependency injection library where types are first-class citizens (users can request instance provided a type), that's why I need either generic aliases or ability to define generic types at runtime.
I think we shouldn't allow this, but should instead allow definition of parameterized type aliases. Having the type variable be explicit makes a big difference in understandability and power.
Here's an example. Let's say we haveVecdefined in some module like so:# in module vector T = TypeVar('T') Vec = List[Tuple[T, T]]
Let's say we want to define
appendandmap. Here's how we'd do that with this proposal:# in module __main__ from vector import Vec, T def append(vec: Vec, elem: T) -> Vec: ... U = TypeVar('U') def map(vec: Vec, f: Callable[[T], U]) -> ???: ...
Note that we have to use
vector.Tto properly defineappend. If we define our ownTlocally, that will cause a confusing error (because it will be considered a different type variable). (Of course, for all expressions not involving aVec,Twill behave just like any other type variable, so I think it's likely to be seen in other contexts too, which would be additionally confusing.) Also note that the fact thatelemis the right element type forvecis implicit rather than explicit (as is the fact thatvecand the return type have the same type). Finally, note that we cannot definemapusing the type alias -- uses of the type alias are confined to those which do not change the parameterized type.Here's how it would look with parameterized type aliases:
# in module __main__ with parameterized type aliases from vector import Vec T = TypeVar('T') U = TypeVar('U') def append(vec: Vec[T], elem: T) -> Vec[T]: ... def map(vec: Vec[T], f: Callable[[T], U]) -> Vec[U]: ...
With parameterized type aliases, all the relationships between parameters have been made explicit, and you can express concepts like
mapusing the type alias.Another problem with the proposal as it stands is that type variables only really make sense in context. For example, you can't have a top-level variable with type
T.Thas to be captured in either class or function scope. The way this happens is already a bit more implicit than I'd like, but this proposal would make it very non-obvious where type variables are hiding.To sum up, parameterized type aliases would be both more powerful and more understandable than non-parameterized type aliases, and I think we should implement them instead.
Reacted by Denis Kasak, Greg Weber, Mitchell Kember and Yifan Zhao- Just note that this would require a corresponding PEP change. TBH I'm not excited about parameterizable type aliases, maybe we should just disallow them in the PEP (as mypy already disallows them).
I agree with @ddfisher that the original
X = List[T]with the result thatXis "linked" to the variableTis confusing and insufficiently expressive. Of course, in this particular case you could simply writeX = Listinstead.The problem with
Vector = Iterable[Tuple[T, T]]with the idea thatVector[int]then meansIterable[Tuple[int, int]]is that you need a way to disambiguate the order of multiple type parameters. Plus it just looks wrong and doesn't fit in with the idea of replacing things with their definitions:(Iterable[Tuple[T, T]])[int]isn't legal. The syntax should be more likeVector[T] = Iterable[Tuple[T, T]]though that exact syntax isn't possible without a previous definition of
Vector.I also agree that the original formulation is not a good idea.
Making generic types indexable if they have type variables could likely be made to work with some heuristics and I believe that it would be at least be marginally useful, but I don't have a strong opinion. I'd suggest finding several real-world use cases where this could be used before making a decision so that we can validate whether this actually looks worth implementing. If the benefit is minor, it might not justify the extra rules and implementation complexity.
If we want to support it, we'd perhaps want to make
List[T][int]valid syntactically, for consistency, even though it seems questionable stylistically. However, somebody might want to write something likeTuple[T, T][Dict[int, List[int]]as a shorter form forTuple[Dict[int, List[int]], Dict[int, List[int]]].For cases like
X[T, T, S][int]where the substitution is ambiguous we can order the type variables based on the order they first appear in the type textually. This would mean thatX[T, T, S][int]is equivalent toX[int, int, S], andX[T, T, S][int, str]is equivalent toX[int, int, str].- Then we need at least an issue in the typing repo reminding us that we probably have to change the PEP.
@rwbarton Maybe I misunderstand you, but it looks like you a talking about an outdated version of
typing.py. Namely,(Iterable[Tuple[T, T]])[int]is perfectly legal since "Revamped Generics" (python/typing#195). The problem of disambiguating the order of multiple type parameters was also solved in that PR by Guido. Few simple examples (assuming that python/typing#296 for nicerrepris merged soon):>>> Vector = Iterable[Tuple[T, T]] >>> Vector[int] typing.Iterable[typing.Tuple[int, int]] >>> Iterable[Tuple[T, U, T]][int] Traceback (most recent call last): ... Too few parameters for typing.Iterable[typing.Tuple[~T, ~U, ~T]]; actual 1, expected 2 >>> Iterable[Tuple[T, U, T]][int, str] typing.Iterable[typing.Tuple[int, str, int]]
I had forgotten about this change as well... Did it go in without much discussion?
@JukkaL Most of discussion about this happened in python/typing#115
I thought the "specification" was well discussed there. The actual implementation maybe was not well discussed, although I like it and it seems quite robust.Oops, I think that I wasn't paying much attention to the original discussion then!
Yeah, the implementation sounds reasonable -- my biggest reservation is how useful this will be in practice, and it's unclear when we are going to have the bandwidth to add support for this to mypy.
@JukkaL
I am interested in this not directly, but mostly because of python/typing#299 (allow subclassingTupleandCallable). The point is that both of these trigger similar code intyping.py:C = List[Tuple[T, T]] C[int] # and class C(List[Tuple[T, T]]): ... C[int]
Probably you are right that the first form might be of less priority for mypy. Both above forms are already allowed in
typing.py, but the following forms are not supported:class C(Tuple[T, T]): ... class C(Tuple[T, ...]): ... class C(Callable[[T], T]): ...
I would like to submit a PR to
typing.pyto allow these forms and usingC[int]for them. As I understand your response on the mentioned issue, you are in favor of this. My idea is just to extend theGenericMetainfrastructure (__origin__,__args__, etc) onTupleandCallable(these two could be seen as special variadic generics). As a side effect of thisTuple[T, T][int]will also work.I think this is rather positive side effect. If people will ask for this and we decide to support this in mypy, then we would not need to change
typing.py. Also IMHO generic aliases improve readability:VecT = Iterable[Tuple[T, T]] def dilate(vec: VecT, scale: T) -> VecT: ... def rotate(vec: VecT, angle: T) -> VecT: ... v: VecT[float] = []
@ilevkivskyi Yeah, I'm not opposed to this.
By the way, shouldn't your example be written like this:
Vec = Iterable[Tuple[T, T]] def dilate(vec: Vec[T], scale: T) -> Vec[T]: ... def rotate(vec: Vec[T], angle: T) -> Vec[T]: ... v: Vec[float] = [](Based on what @ddfisher wrote above.)
By the way, shouldn't your example be written like this:
Good point! Indeed, it looks much cleaner and
Vec[T]is also supported intyping.pyalready.- Re: Vec[T] we should update the PEP example. Re: subclassing Tuple, let's implement it first and add to the PEP once implemented. Seeing it as variadic type var is great. Re: subclassing Callable, I foresee some issues related to the argument list.…--Guido (mobile)
- added a commit that references this issue
on Nov 3, 2016
This code should be okay:
It should be equivalent to this:
This was suggested by Guido, "based on the general equivalence in Python of
to
".