Repository navigation
Allow variadic generics #193
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
Tswould be valid in at least these contexts:Tuple[Ts](the only argument toTuple)Callable[Ts, X](the first argument toCallable)*args, such as*args: TsIt'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:
zipcontextlib.contextmanagersixolet 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
Queryobjects, which vary in the types of results they produce. I also have anexecutefunction, which takes in someQuerys 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,
executereturns 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
eachas a possibility to mean what you're usingonefor above?sixolet commentedon Jul 26, 2016
Unless, say,
.allis the default, and you don't have to say it, and you have to say.eachat every layer you mean to map the types along.sixolet commentedon Jul 26, 2016
Here's "all is the default, you have to specify each"
Here's "each is the default, you have to specify all"
gvanrossum commentedon Jul 26, 2016
42 remaining items
dnaaun commentedon Jul 27, 2020
Hi all. Thank you for mypy!
So, what is the plan going forward regarding variadic generics?
webhooks: Update comment about typing the webhook decorator.
webhooks: Update comment about typing the webhook decorator.
webhooks: Update comment about typing the webhook decorator.
webhooks: Update comment about typing the webhook decorator.
Delete with_cursor decorator
Delete with_cursor decorator
mrahtz 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.pyin 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
MultiFieldwould 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
Queryuse 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 theQuerycase 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!
Conchylicultor commentedon Mar 15, 2022
Does
TypeVarTuplesupport 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.Mapoperator 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!).
webhooks: Update comment about typing the webhook decorator.