-
Notifications
You must be signed in to change notification settings - Fork 263
Description
C++11 recently introduced the notion of variadic templates, which I believe Python could benefit from in a simplified form.
The idea is that you can have a generic class with a variable number of type variables, like typing.Tuple[]
has. Here is a real-world variadic-generic class which is not Tuple
; it is variadic in TagVar
. As you can see, TagVar
only appears in tuple contexts. Those tuples are sometimes heterogenous (Caution: annotations are a homegrown 3.4-compatible mishmash of nonsense), so repeating TagVar
as shown is actually incorrect (but the closest approximation I could find).
Here's one possible syntax:
class MultiField(AbstractField[GetSetVar], Generic[(*TagVar,)]):
def __init__(self, nbt_names: ty.Sequence[str], *, default:
GetSetVar=None) -> None:
...
@abc.abstractmethod
def to_python(self, *tags: (*TagVar,)) -> GetSetVar:
...
@abc.abstractmethod
def from_python(self, value: GetSetVar) -> ty.Tuple[(*TagVar,)]:
...
This is syntactically valid in Python 3.5 (if a bit ugly with the parentheses and trailing comma, which cannot be omitted without language changes), but doesn't currently work because type variables are not sequences and cannot be unpacked. It could be implemented by adding something like this to the TypeVar class:
def __iter__(self):
yield StarredTypeVar(self)
StarredTypeVar would be a wrapper class that prefixes the repr with a star and delegates all other functionality to the wrapped TypeVar.
Of course, syntax isn't everything; I'd be fine with any syntax that lets me do this. The other immediately obvious syntax is to follow the TypeVar with an ellipsis, which conveniently does not require changes to typing.py. However, that might require disambiguation in some contexts (particularly since Tuple
is likely to be involved with these classes).
Activity
gvanrossum commentedon Mar 24, 2016
Hmm... why not make this a special property of type variables.Tthen you wouldn't need the funny syntax, you could just use a type variable that has a keyword saying it is variadic.
But maybe the bigger question is how exactly we should type check this.
NYKevin commentedon Mar 24, 2016
This isn't obviously objectionable, but for the following reasons I'm not sure it's actually a good idea. First, I would like to point out that I failed to notice a third possible syntax, which doesn't require the trailing comma:
I'll return to this syntax in a moment.
We should enforce that the variable only appears as the sole argument to Tuple (with a trailing ellipsis?), as a variable of a generic class, possibly (?) in the instantiation of a generic class other than Tuple, or as the type of a variadic parameter (
*args
,**kwargs
). The generic class case is complicated, however, because there might be multiple variadic type variables in play under multiple inheritance. Even if there aren't, it could be difficult to parse.We can overcome both problems by requiring specialized syntax when the generic class is subclassed or instantiated:
Note the extra pair of brackets. They indicate where the variadic typevar begins and ends, which removes any ambiguity when there are multiple variadic typevars, and simplifies parsing when there are non-variadic typevars in the same class (as in this case).
For reasons of uniformity, I would recommend we use the bracket syntax I showed above rather than making variadic-ness a property of the type variable. That makes instantiation and declaration look more like one another, and seems more intuitive to me.
JukkaL commentedon Mar 26, 2016
Variadic generics would likely be useful at least occasionally. It would make sense to generalize them also to argument types in
Callable
. I remember that there are a few places in the std lib stubs where these would have let us have a more precise type for a library function. The canonical example might be something like this (in my counter-proposal being variadic is a property of a type variable, as it seems more readable to me):In my proposal a variadic type variable
Ts
would be valid in at least these contexts:Tuple[Ts]
(the only argument toTuple
)Callable[Ts, X]
(the first argument toCallable
)*args
, such as*args: Ts
It's less obvious how to generalize this to user-defined generic types. Maybe like this:
The limitation would be that in order to have both variadic and non-variadic arguments, you'd have to create a dummy wrapper type for the variadic args:
The apparent awkwardness of the above example probably wouldn't matter much as I expect this use case to be very rare.
There are additional implementation details that a type checker should probably get right to make this useful, but I'm not even trying to enumerate them here. The implementation would likely be tricky, but probably not excessively so.
I'm still unconvinced that this is a very important feature -- look at how long it took C++ to pick up this feature. I'd propose looking for more use cases to justify variadic generics and once we have a sufficient number of use cases collected here, we'd send this to python-ideas@ for discussion.
JukkaL commentedon Apr 5, 2016
Examples where these could be useful:
zip
contextlib.contextmanager
sixolet commentedon Jul 25, 2016
@JukkaL There's one extension to your proposal that would solve a problem I've been trying to figure out: I have some
Query
objects, which vary in the types of results they produce. I also have anexecute
function, which takes in someQuery
s and would like to return a tuple, with one result per Query it was passed.I am not at all wed to this syntax for mapping over variadic types variables, but I want to be able to do it, and I think it should be a thing we should consider when considering variadic type variables.
gvanrossum commentedon Jul 25, 2016
It's not very clear from that example that execute() returns a tuple. I think Jukka's proposal would have you write Tuple[Rs] for the return type. I also wish the map() functionality was expressible as part of the signature of execute() rather than in the definition of Qs.
Anyway, this would be shorthand for an infinite sequence of definitions for execute(), like this, right?
NOTE: The special case for the first overload is not part of the special semantics for variadic type variables; instead there could be two overload variants, one for a single query, one for 2 or more.
sixolet commentedon Jul 25, 2016
Yes,
execute
returns aTuple[Rs]
and I simply was posting too quickly to check my work, apologies.sixolet commentedon Jul 25, 2016
And yes, this would be exactly that infinite series of overloads.
sixolet commentedon Jul 26, 2016
Making the type mapping in the argument list would be nice, but requires some care as to exactly where you are parameterizing a type over each element of your variadic type variable, and where you are parameterizing a type using all elements of your variadic type variable.
For example, I'd love to be able to write
Query[Rs]
and mean "a variadic type variable that is a query type for each result type inRs
" but then I want to also be able to write something likeTuple[Rs]
meaning "a tuple of every return type in Rs".One possibility is, borrowing some stars and parens from @NYKevin to have, I think, somewhat of a different meaning:
gvanrossum commentedon Jul 26, 2016
So maybe the notation ought to reflect that, and we should be able to write
Where .one and .all try to give hints on how to expand these.
sixolet commentedon Jul 26, 2016
Ooh, I like not having it be some kind of obtuse operator but rather english words. Consider
each
as a possibility to mean what you're usingone
for above?75 remaining items
Annotated
to a subscriptable type #779mrahtz commentedon Feb 20, 2021
A few of us have actually been working on a draft of a PEP for variadics generics since last year, PEP 646. Eric Traut has very kindly contributed an initial implementation in Pyright, Pradeep Kumar Srinivasan has been working on an implementation in Pyre, and we're working on the additions to
typing.py
in this pull request. Sorry for the late notice in this thread; I do remember seeing this thread a long time ago but apparently it didn't stick in my memory.@NYKevin As of the current draft, I think your use case with
MultiField
would look like this using the current PEP draft:@sixolet I think you've seen the thread in typing-sig about 646, but for the sake of other people reading this thread: the
Query
use case isn't supported by the current PEP; it used to be, but the PEP was getting too big, so we postponed this for a future PEP. The section we cut out is in this doc. Here's what theQuery
case would look like if we did use the proposal in that doc: (though of course there's still plenty of room for more discussion on this; there are some interesting proposals in this thread!)@seaders I think your use case should be possible with:
In any case, if you have any feedback on the current draft of the PEP, please do drop by the thread in typing-sig and leave us a message. Cheers!
asyncio.gather
bad type information (pyright) microsoft/pylance-release#1913Conchylicultor commentedon Mar 15, 2022
Does
TypeVarTuple
support distribution inside other types (likedict
) ?For example:
(*args: *dict[str, Ts]) -> dict[str, tuple[*Ts]]
?I'm trying to annotate the following function, but I'm not sure this is supported. It's unclear from the PEP as there's no example.
JelleZijlstra commentedon Mar 15, 2022
@Conchylicultor this is unsupported. There's been talk of adding a
typing.Map
operator that would support something like this, but I don't think there's any concrete progress yet.Closing this issue as PEP 646 has been accepted and implemented (thanks @mrahtz and @pradeep90!).