-
Notifications
You must be signed in to change notification settings - Fork 237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TypeVarTuple Transformations Before Unpack #1216
Comments
@llchan Yes, we plan to add a follow-up PEP to PEP 646 with support for such mapping (hopefully, later this year). We cut it from the original PEP because the PEP was already pretty complicated. |
Great to hear. If/when there's an official tracking issue or link to a discussion it would be helpful to include a reference here (and feel free to close this if it's superceded). |
Hi @pradeep90, It would be great if it also will be possible to "unwrap" For instance: from typing import TypeVarTuple, Generic, TypeVar
from dataclasses import dataclass
T = TypeVar("T")
@dataclass(frozen=True)
class Wrapper(Generic[T]):
wrapped: T
Ts = TypeVarTuple("Ts")
def unwrap(*objs: *Wrapper[Ts]) -> tuple[*Ts]:
return tuple(obj.wrapped for obj in objs) I would love to hear your opinion regarding this feature. |
I ran into this use case today, trying to annotate Qt for Python's Has there been any development regarding this topic since it was opened? |
This function will replace the current `Select` implementation with the following improvements: * Proper type hinting by using the new helper type guard `selected_from()`. * Fixes potential starvation issues. * Simplifies the interface by providing values one-by-one. * Simplifies the implementation, so it is easier to maintain. There are some other improvements we would have liked to be able to make but was difficult. For example, typing for `select()` is tricky. We had the idea of using a declarative design, something like: ```python class MySelector(Selector): receiver1: x.new_receiver() receiver2: y.new_receiver() async for selected in MySelector: if selected.receiver is receiver1: # Do something with selected.value elif selected.receiver is receiver1: # Do something with selected.value ``` This is similar to `Enum`, but `Enum` has special support in `mypy` that we can't have. With the current implementation, the typing could be slightly improved by using `TypeVarTuple`, but we are not because "transformations" are not supported yet, see: python/typing#1216 Also support for `TypeVarTuple` in general is still experimental (and very incomplete in `mypy`). With this we would also probably be able to properly type `select` and *maybe* even be able to leverage the exhaustiveness checking of `mypy` to make sure the selected value is narrowed down to the correct type to make sure all receivers are handled, with the help of `assert_never` as described in: https://docs.python.org/3.11/library/typing.html#typing.assert_never We also explored the possibility of using `match` to perform exhaustiveness checking, but we couldn't find a way to make it work with `match`, and `match` is not yet checked for exhaustiveness by `mypy` anyway, see: python/mypy#13597 Signed-off-by: Leandro Lucarella <[email protected]>
This function will replace the current `Select` implementation with the following improvements: * Proper type hinting by using the new helper type guard `selected_from()`. * Fixes potential starvation issues. * Simplifies the interface by providing values one-by-one. * Simplifies the implementation, so it is easier to maintain. There are some other improvements we would have liked to be able to make but was difficult. For example, typing for `select()` is tricky. We had the idea of using a declarative design, something like: ```python class MySelector(Selector): receiver1: x.new_receiver() receiver2: y.new_receiver() async for selected in MySelector: if selected.receiver is receiver1: # Do something with selected.value elif selected.receiver is receiver1: # Do something with selected.value ``` This is similar to `Enum`, but `Enum` has special support in `mypy` that we can't have. With the current implementation, the typing could be slightly improved by using `TypeVarTuple`, but we are not because "transformations" are not supported yet, see: python/typing#1216 Also support for `TypeVarTuple` in general is still experimental (and very incomplete in `mypy`). With this we would also probably be able to properly type `select` and *maybe* even be able to leverage the exhaustiveness checking of `mypy` to make sure the selected value is narrowed down to the correct type to make sure all receivers are handled, with the help of `assert_never` as described in: https://docs.python.org/3.11/library/typing.html#typing.assert_never We also explored the possibility of using `match` to perform exhaustiveness checking, but we couldn't find a way to make it work with `match`, and `match` is not yet checked for exhaustiveness by `mypy` anyway, see: python/mypy#13597 Signed-off-by: Leandro Lucarella <[email protected]>
This would be great, and I'm interested for a workaround for |
It would also be nice to be able to this to TypedDicts and ParamSpecs (and ParamSpecArgs and ParamSpecKwargs) |
As an aside would there be a way to map a def buy_items[*AppShopItemTs: AppShopItem](self, *items: *AppShopItemTs) -> ...: # not sure what the return should look like
"""Buy the ``items`` from the in-app store.
Returns: The items in the user's inventory, bought from the store""" def buy_items[*AppShopItemTs: AppShopItem](self, *items: *AppShopItemTs) -> tuple[*(Item for _ in range(len(AppShopItemTs)))]: is a cute idea and I think I have seen generator expressions as an idea for a shorthand to Map come up a few times |
We definitely need this kind of typing.Map-like feature |
Probably duplicate of #1273, specialized to This would indeed be very nice, another use-case: column-oriented tables like class Table[*Dtypes]:
column_names: tuple[*(str for dtype in *Dtypes)]
columns: tuple[*(Series[dtype] for dtype in *Dtypes)]
table: Table[int, str] = Table({"foo" : [1,2,3]}, "bar": ["x", "y", "z"])
table.column_names # -> tuple[str, str]
table.columns # -> tuple[Series[int], Series[str]] EDIT: Updated using the proposed comprehension syntax by @zen-xu for easier readability |
Not sure if this is exactly the same structure as others in this thread, but here's possibly another use-case of this sort of thing: I have a function def my_func[*Ts](
some_callables: list[
callable[
[],
OneOf[*Ts] # or however one would express this - maybe "Map", or even "T in Ts" ?
]]
) -> Union[*Ts]:
return random.choice(some_callables)()
def f() -> int:
return 1
def g() -> str:
return ""
x: int | str = my_func([f, g]) # the type hint here should be inferrable I think that |
Here is another related example: def prod_fn[*Us, *Vs](*funcs: ???) -> Callable[[*Us], tuple[*Vs]]:
r"""Cartesian Product of Functions.
It is assumed every function takes a single positional argument.
"""
def __prod_fn(*args: *Us) -> tuple[*Vs]:
"""Argument is a tuple with the input for each function."""
return tuple(f(arg) for f, arg in zip(funcs, args))
return __prod_fn Here,
|
Mmh, I find that notation a bit confusing. The left |
Hm, I guess notationally something like: Notably, such a construct is also required to even type hint class map[F: Callable[[*Ts], S], *Ts]:
@overload
def __new__(cls, func: F, /) -> Never: ... # need at least 1 iterable.
@overload
def __new__(cls, func: F, /, *iterables: *Map[Iterable, Ts]) -> Self: ...
def __iter__(self) -> Self: ...
def __next__(self) -> S: ... |
Are there any plans on adding this feature? It was mentioned that they were planning to release a follow-up PEP later in 2022, but it has been two years. Has this been abandoned or forgotten? |
There are no concrete plans currently. Keep in mind that it took nearly two years for mypy to implement support for the existing If you're interested in proposing extensions to |
Alright, thanks for the quick response! I am interested in championing a new PEP for this, although am unsure if I have enough experience to do so. I will have to look into it. |
If T = TypeVar('T')
Ts = TypeVarTuple('Ts')
class Example(Generic[*Ts]):
def __init__(self, *types: *type[Ts]) -> None:
self.types = types
def create_defaults(self) -> tuple[T for T in Ts]:
return tuple(t() for t in self.types)
def create_lists(self) -> tuple[list[T] for T in Ts]:
return tuple([t()] for t in self.types)
def create_callables(self) -> tuple[Callable[[], T] for T in Ts]:
return tuple(lambda: t() for t in self.types)
e = Example(int, str)
assert_type(e.create_defaults(), tuple[int, str])
assert_type(e.create_lists(), tuple[list[int], list[str]])
assert_type(e.create_callables(), tuple[Callable[[], int], Callable[[], str]]) In addition, it also supports for type guards. @overload
def create(self) -> tuple[ContainerA[T] for T in Ts if issubclass(T, A)]: ...
@overload
def create(self) -> tuple[ContainerB[T] for T in Ts if issubclass(T, B)]: ... |
For people interested in |
@ahmed-mahran It looks like your also trying to implement higher-kinded typing (HKT) with your examples, see #548 . This made me realize that this issue is actually about variadic HKT. |
SO questions requesting that feature https://stackoverflow.com/q/73200382/1658617 |
It would be useful to be able to apply type transformations to
TypeVarTuple
's types. Some motivating examples:At a high level, everything between the
*
and theTypeVarTuple
it expands would be applied per type in the tuple before it's unpacked.There are some decisions to be made about how the expansion is done if there are multiple
TypeVarTuple
s in the expand expression, but I think it can be supported and I think the intuitive thing would be to zip up all theTypeVarTuple
s. For example,tuple[*tuple[Ts, Us]]
withTs = (int, str)
andUs = (float, bytes)
would betuple[tuple[int, float], tuple[str, bytes]]
, with the implementation enforcing equal-lengthTypeVarTuple
s.If there's a workaround for this using the existing logic, let me know. My current use case involves keeping a reference to the runtime types, so
*type[Ts]
is what I'm looking for at the moment.The text was updated successfully, but these errors were encountered: