> Could you summarize your proposal in a few lines?
Use PEP 593 `Annotated` the way Adrian has proposed, but with an additional
parameter which maps the type guard on the given function parameter name:
```python
def check_int_and_str(x, y) -> Annotated[bool, TypeGuard(int, "x"),
TypeGuard(str, "y")]:
return isinstance(x, int) and isinstance(y, str)
```
Provide the following shortcuts:
- `TypeGuard` second parameter can be omitted, i.e. `TypeGuard(int)`; the type
guard then apply to the first parameter of the function
- `TypeGuard` can be used like a type, i.e. `def is_int(x) -> TypeGuard[int]`
(and the type guard apply then to the first parameter), but it has to be
evaluated to `Annotated[bool, TypeGuard(int)]` at runtime (by implementing
`TypeGuard.__class_getitem__`).
To be interpreted by type checkers, type guard must be evaluated as a boolean
condition (exactly as the current proposal):
```python
def is_int(x) -> TypeGuard[int]:
return isinstance(x, int)
x = ...
if is_int(x):
# reveal_type(x) -> int
while is_int(x):
# reveal_type(x) -> int
def foo(x):
assert is_int(x)
# reveal_type(x) -> int
```
Restriction: `TypeGuard` second parameter must be a string literal
corresponding to the name of a function whose return type is annotated, or must
be omitted.
A possible implementation of `TypeGuard` would be:
```python
class TypeGuard:
def __init__(self, tp, param=None):
self.tp = tp
if param is not None and not isinstance(param, str):
raise TypeError("Type guard parameter mapping must be a string")
self.param = param
def __class_getitem__(self, item):
return Annotated[bool, TypeGuard(item)]
```
(It's not really different than my first mail, but I've fixed my mistake by
using `__class_getitem__` instead of `__getitem__)
I see the following advantages of using PEP 593:
- it will not break type checkers implementation when it will be released (at
the condition of using the raw `Annotated` form and not the second shortcut)
- the second shortcut makes it as easy to use than the current proposal using a
special form
- it allows supporting type guard for multiple parameters (but this use case
should be quite occasional)
- it will not break tools using runtime introspection (I think the case where
return type of type guard predicate is inspected at runtime will also be very
uncommon)
- it allows returning other thing than a boolean, for example an `Optional`
value:
```python
def get_bar(foo) -> Annotated[Optional[Bar], TypeGuard(Foo)]:
return foo.bar if isinstance(foo, Foo) else None
foo = …
if bar := get_bar(foo):
# foo will be inferred as Foo
```
but this use case should be quite occasional too (and it's kind of tricky
because if the type guard function returns an `Optional[int]` and the return is
`0`, the type guard will still *fail*)
- lastly, it would be a good way to introduce PEP 593 into the standard
library, as I see other use cases for it (especially `ClassVar` and `InitVar`),
but that's an other subject.
The drawbacks:
- `TypeGuard` will not be interpreted when present in a `Callable` signature,
and the following example will not be possible:
```python
def infer_list(l: list, guard: Callable[[Any], TypeGuard[T]]) ->
TypeGuard[list[T]]:
return all(map(guard, l))
```
but this use case should also be occasional (especially because of the lack of
generic type var, that make impossible to replace `list` by a `TypeVar`). By
the way, I don't know if things like this are possible in TypeScript.
- your quote:
> I see PEP 593 as a verbose solution to the problem "how do we use annotations
> for static typing and for runtime metadata simultaneously". Type guards solve
> a problem that's entirely in the realm of static typing, so IMO it would be
> an abuse of Annotated.
because I quite agree with you; contrary to `ClassVar` and `InitVar` which are
used at runtime, `TypeGuard` should only be used by type checkers.
So there are pros and cons, but they are mostly about occasional use cases.
Personally, I would be more in favor of PEP 593, but the current proposal if
also fine to me.
_______________________________________________
Python-Dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/BISHSBSOYAYRY736P7RRONNVJWVWGJTY/
Code of Conduct: http://python.org/psf/codeofconduct/