> 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 -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/BISHSBSOYAYRY736P7RRONNVJWVWGJTY/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to