[Python-Dev] Re: PEP 647 (type guards) -- final call for comments

2021-02-16 Thread Joseph Perez
> 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/


[Python-Dev] Re: PEP 647 (type guards) -- final call for comments

2021-02-16 Thread Joseph Perez
I've proposed PEP 593 `Annotated` too, but in the typing-sig mailing list: 
https://mail.python.org/archives/list/typing-...@python.org/message/CVLLRWU7MU7T2AMC4P7ZEG4IMJF6V5UL/
and Guido had the following answer:
> 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.

(I've also written in the mail about checked cast as an alternative solution to 
type guard.)

> For the most extensible approach both -> TypeGuard(...)  and
-> Annotated[bool, TypeGuard(...)] could be allowed, which would open the
path for future non-type-annotations, which could be used regardless of whether 
the code
is type-annotated.
I've proposed a possible implementation in my mail linked above.
___
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/B3QZTPW6S6LHQKX476VRYAWEVMZ26VHH/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: PEP: Deferred Evaluation Of Annotations Using Descriptors

2021-02-16 Thread Joseph Perez
PEP 649 doesn't prevent to use stringified annotations (Larry has previously 
mentioned it in its response to Paul Bryan), and they seem to be still required 
when `if TYPE_CHECKING:` is used, despite the PEP claim.

And my last message bring some use cases where strings are also required 
(notably, in recursive dataclasses).
___
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/7QA3Z4CNYHW3GOEDAST6WW37O5OUJRW6/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: PEP: Deferred Evaluation Of Annotations Using Descriptors

2021-02-16 Thread Joseph Perez
If I've understood the PEP correctly, it would cause the following simple 
example to fail:
```python
from dataclasses import dataclass

@dataclass
class User:
name: str
friends: list[User]
```
In fact, when the `dataclass` decorator is called, `User` class is not yet 
added to the module namespace, so when class `__annotations__` descriptor will 
be called inside the decorator, it will raise a `NameError` because of 
`friends` recursive annotation.

By the way, in the example given by the PEP:
```python
def foo(x: int = 3, y: MyType = None) -> float:
 ...
class MyType:
 ...
```
if `foo` is decorated with a decorator calling `__annotations__` or 
`get_type_hints`, it will fail too.

Using stringified annotations would prevent `NameError` to be raised, but it 
really mitigates the PEP claim that 
> This PEP also solves the forward reference problem

Not only this PEP doesn't solve (again, if I understand it correctly) the 
forward reference problem, but also it makes it a lot more tricky. And I think 
my first example is not so uncommon.
___
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/CDCDXBKQF6ALDEM4EEUGEK654XOKJG3I/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: PEP: Deferred Evaluation Of Annotations Using Descriptors

2021-02-16 Thread Joseph Perez
> Please note that this is a thread about PEP 649.
> 
> If PEP 649 accepted and PEP 563 dies, all such idioms breaks
annotation completely.
> 
> Users need to import all heavy modules and circular references used
only type hints, or user can not get even string form annotation which
is very useful for REPLs.

I don't see why `if TYPE_CHECKING:` idiom breaks annotations with PEP 649. 
There will be no error as long as `__annotations__` descriptor is not called.
And currently in 3.9 (with or without `from __future__ import annotations`), 
the issue is the same: you `get_type_hints` fails if some of the types in the 
annotations have been imported in a `if TYPE_CHECKING:` block. 

> Hm, that's a rather serious problem with Larry's PEP 649 compared to from
__future__ import annotations, actually.

As I've written above, this is not a new issue, and neither this PEP nor PEP 
563 can fix it.
___
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/PVPCJV6GATMRACXIPPNSNCUV7OFGDEU3/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: PEP: Deferred Evaluation Of Annotations Using Descriptors

2021-02-14 Thread Joseph Perez
By the way, without adding an other constant, `__debug__` can also be used. It 
discards runtime overhead when it matters, in optimized mode.
___
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/FSNLQARSUQ3DP4X3DD4UMEK2TAIUNJHD/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: PEP: Deferred Evaluation Of Annotations Using Descriptors

2021-02-14 Thread Joseph Perez
> How about having a pseudo-module called __typing__ that is
> ignored by the compiler:
> 
> from __typing__ import ...
> 
> would be compiled to a no-op, but recognised by type checkers.

If you want to do run-time typing stuff, you would use
There is already a way of doing that: `if typing.TYPE_CHECKING: ...` 
https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING
But yes, the issue with it is that this constant is defined in the `typing` 
module … 

However, I think this is a part of the solution. Indeed, the language could 
define another builtin constants, let's name it `__static__`, which would 
simply be always false (at runtime), while linters/type checkers would use it 
the same way `typing.TYPE_CHECKING` is used:
```python
if __static__:
import typing
import expensive_module 
```
___
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/ORL4B4RISFYIROSPGL4B4AVNWEOXP2TS/
Code of Conduct: http://python.org/psf/codeofconduct/