[Python-Dev] Re: PEP 647 (type guards) -- final call for comments
> 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
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
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
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
> 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
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
> 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/