Using you example:
from typing import overload
@overload
def fooify(x: int) -> int:
...
@overload
def fooify(x: list[int]) -> list[int]:
...
def fooify(x: list[int] | int) -> list[int] | int:
if isinstance(x, list):
return [fooify(_x) for _x in x]
return x * 2
[0] https://docs.python.org/3/library/typing.html#overload T = TypeVar("T", bound=int | list[int])
def(x: T) -> T:Meanwhile it's not even possible to express such things in other static type systems. So I'm not exactly an unhappy customer, but it does put certain things tantalizingly close, but still out of reach without a ton of clunky boilerplate and LoC explosion.
what do you mean? It seems relatively straightforward.
type CompoundInt = int | list[CompoundInt]
def fooify(x: CompoundInt) -> CompoundInt:
if isinstance(x, int):
return x * 2
else:
return list(map(fooify, x))
print(fooify([1, [3, 4, 5], [6, 7, 8], [[[4, 4, 4]]]]))
This uses the `type` keyword introduced in 3.12. Unfortunately Mypy doesn't support it yet :( so this workaround can be used instead: from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
CompoundInt = int | list[CompoundInt]In fact, I think TypeScript will, for your given example, with the 'else' clause accurately identify the type of x to be a float if it's a union like the one I wrote down above.
In fact, in a lot of languages, you can't tell this is a float, statically. It would work with whatever type was passed in that supports multiplication. And return the appropriate type. Right?
def fooify(x: int | list[int])
If x is a list, the result is a list
I don’t think that the type system can describe this.
For example:
I guess this is to make scripting more forgiving.In typical typed languages, you would have two functions instead:
But in the Python community, it's common to have a big function with many behaviors. However, then the type annotations cannot be so precise: Not an experienced Python dev, so curious how this works in practice.