PEP 647 (type guards) -- final call for comments
I think we have reached consensus on PEP 647 in typing-sig. We have implementations for mypy and pyright, not sure about the rest. This PEP does not affect CPython directly except for the addition of one special item (TypeGuard) to typing.py -- it would be nice to get that in the 3.10 stdlib. I'm CC'ing python-dev here to see if there are any further comments; if not, we can initiate the approval process by creating an issue at https://github.com/python/steering-council. -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
I'm quoting here the content of my two comments on the mypy issue https://github.com/python/mypy/issues/5206: # Quote starts here Why do we need a boolean return? A direct solution to this issue would be to implement type-guard functions to return the checked argument, like a checked cast in fact: ```python def as_integer(x) -> int: if not isinstance(x, int): raise ValueError("not an int") return x ``` If-else logic can simply be achieved using try-catch-else blocks. ```python try: checked_x = as_integer(x) except ValueError: ... else: ... # use checked_x as an integer in this block ``` This checked-cast functions can also be used as expression, which is convenient for case like `expect_an_int(as_integer(x))` (but with risks of exception mix up between expect_an_int and as_integer). Checked-cast functions could also return an `Optional` in order to avoid exception (but that's maybe not suited for some cases) ```python def as_integer(x) -> Optional[int]: return x if isinstance(x, int) else None if (checked_x := as_integer(x)) is not None : ... else: ... ``` Moreover, if several parameters needs to be type-checked, a tuple returns can do the job: ```python def check_int_and_str(x, y) -> tuple[int, str]: if not isinstance(x, int) or not isinstance(y, str): raise ValueError("bad types") return x, y checked_x, checked_y = check_int_and_str(x, y) ``` But yes, this solution imply to assign an additional variable (with an additional name), so it's heavier, but **it already works** out of the box and do the job, no PEP required. That's being said, if boolean type-guard functions have to be implemented in the language (and I would be happy to use them to replace my heavier checked-cast), why not using [PEP 593](https://www.python.org/dev/peps/pep-0593/) `Annotated`? By adding a standard type annotation (for `Annotated`), one could write something like ```python from typing import Annotated, TypeGuard def is_integer(x) -> Annotated[bool, TypeGuard(int, "x")]: # map the type-guard to the corresponding parameter return isinstance(x, int) ``` That could allow type-guarding of several parameters: ```python def check_int_and_str(x, y) -> Annotated[bool, TypeGuard(int, "x"), TypeGuard(str, "y")]: return isinstance(x, int) and isinstance(y, str) ``` Using a PEP 593 type annotation instead of a whole new type has the advantage of **not impacting every tools using type annotation** (as metadata can just be ignored). This is also exactly the purpose of PEP 593 as it states:
a type T can be annotated with metadata x via the typehint Annotated[T, x]. **This metadata can be used for either static analysis** or at runtime.
The type-guard is indeed a metadata of the function result, but the function still returns a `bool`. But yes, this solution seems to be a little bit heavier than @vbraun proposal or [PEP 647](https://www.python.org/dev/peps/pep-0647/), but nothing prevent the specification and implementation of my proposal to provide the following shortcuts: - when no parameter name is passed to `TypeGuard`, i.e. `TypeGuard(int)`, it applies to the first parameter (or the second in case of a method) - `TypeGuard` has a `__getitem__` method which gives the following result: `TypeGuard[T] == Annotated[bool, TypeGuard(T)]` A simple implementation 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 __getitem__(self, item): return Annotated[bool, TypeGuard(item)] ``` It would then possible to write ```python def is_integer(x) -> TypeGuard[int]: ... # which would give in fact `def is_integer(x) -> Annotated[bool, TypeGuard(int)]` # which would thus be equivalent to `def is_integer(x) -> Annotated[bool, TypeGuard(int, "x")]` ``` As easy, but more powerful (support arbitrary parameters), and again, less complexity (no additional `SpecialForm`), less impact on existent tools. # Quote ends here To sum up, checked cast can already "do the job", at the cost of additional variable declaration and exception-catching/optional-checking. Is the PEP worth the cost? In this case, I think a new "special form" type is not the best way to implement type guard, and I would rather use PEP 593 the way I've described above.
On Sun, Feb 14, 2021 at 2:21 PM Joseph Perez <joperez@hotmail.fr> wrote:
I'm quoting here the content of my two comments on the mypy issue https://github.com/python/mypy/issues/5206:
# Quote starts here Why do we need a boolean return?
A direct solution to this issue would be to implement type-guard functions to return the checked argument, like a checked cast in fact: ```python def as_integer(x) -> int: if not isinstance(x, int): raise ValueError("not an int") return x ``` If-else logic can simply be achieved using try-catch-else blocks. ```python try: checked_x = as_integer(x) except ValueError: ... else: ... # use checked_x as an integer in this block ```
The existing logic in type checkers does not deal with control flow due to try/except/else, because it's hard to reason about when an exception can or can't happen. Even if we could address this, to human readers try/except/else is an unwieldy construct to grasp compared to if/else, plus the latter allows for multiple elif clauses as well. Given the existing support for constructs like `isinstance(x, C)`, `x is not None` and such I think it's reasonable to also allow user-defined expressions to influence the control flow analysis that already exists in type checkers. Note that it's also easy to implement your as_integer() using a type guard: ``` def is_integer(x: object) -> TypeGuard[int]: return isinstance(x, int) def as_integer(x: object) -> int: if is_integer(x): return x else: raise ValueError(...)
This checked-cast functions can also be used as expression, which is convenient for case like `expect_an_int(as_integer(x))` (but with risks of exception mix up between expect_an_int and as_integer).
Checked-cast functions could also return an `Optional` in order to avoid exception (but that's maybe not suited for some cases) ```python def as_integer(x) -> Optional[int]: return x if isinstance(x, int) else None
if (checked_x := as_integer(x)) is not None : ... else: ... ```
Moreover, if several parameters needs to be type-checked, a tuple returns can do the job: ```python def check_int_and_str(x, y) -> tuple[int, str]: if not isinstance(x, int) or not isinstance(y, str): raise ValueError("bad types") return x, y
checked_x, checked_y = check_int_and_str(x, y) ```
But yes, this solution imply to assign an additional variable (with an additional name), so it's heavier, but **it already works** out of the box and do the job, no PEP required.
That's not the problem the proposal is solving.
That's being said, if boolean type-guard functions have to be implemented in the language (and I would be happy to use them to replace my heavier checked-cast), why not using [PEP 593]( https://www.python.org/dev/peps/pep-0593/) `Annotated`? By adding a standard type annotation (for `Annotated`), one could write something like ```python from typing import Annotated, TypeGuard
def is_integer(x) -> Annotated[bool, TypeGuard(int, "x")]: # map the type-guard to the corresponding parameter return isinstance(x, int) ``` That could allow type-guarding of several parameters: ```python def check_int_and_str(x, y) -> Annotated[bool, TypeGuard(int, "x"), TypeGuard(str, "y")]: return isinstance(x, int) and isinstance(y, str) ``` Using a PEP 593 type annotation instead of a whole new type has the advantage of **not impacting every tools using type annotation** (as metadata can just be ignored). This is also exactly the purpose of PEP 593 as it states:
a type T can be annotated with metadata x via the typehint Annotated[T, x]. **This metadata can be used for either static analysis** or at runtime.
The type-guard is indeed a metadata of the function result, but the function still returns a `bool`.
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.
But yes, this solution seems to be a little bit heavier than @vbraun proposal or [PEP 647](https://www.python.org/dev/peps/pep-0647/), but nothing prevent the specification and implementation of my proposal to provide the following shortcuts: - when no parameter name is passed to `TypeGuard`, i.e. `TypeGuard(int)`, it applies to the first parameter (or the second in case of a method) - `TypeGuard` has a `__getitem__` method which gives the following result: `TypeGuard[T] == Annotated[bool, TypeGuard(T)]`
A simple implementation 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 __getitem__(self, item): return Annotated[bool, TypeGuard(item)] ```
It would then possible to write ```python def is_integer(x) -> TypeGuard[int]: ... # which would give in fact `def is_integer(x) -> Annotated[bool, TypeGuard(int)]` # which would thus be equivalent to `def is_integer(x) -> Annotated[bool, TypeGuard(int, "x")]` ``` As easy, but more powerful (support arbitrary parameters), and again, less complexity (no additional `SpecialForm`), less impact on existent tools. # Quote ends here
To sum up, checked cast can already "do the job", at the cost of additional variable declaration and exception-catching/optional-checking. Is the PEP worth the cost?
In this case, I think a new "special form" type is not the best way to implement type guard, and I would rather use PEP 593 the way I've described above.
Ideally we'd use new syntax, but (like many things around static typing for Python, starting with PEP 484 itself) we have to compromise. TypeGuard is ersatz syntax, just like TypeVar, Union, Callable and many others. It stands to reason that the constructs that are most commonly used are the first ones to get actual new syntax assigned to them, and Callable is next in line. TypeGuard still has to prove itself. -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
The existing logic in type checkers does not deal with control flow due to try/except/else, because it's hard to reason about when an exception can or can't happen. Even if we could address this, to human readers try/except/else is an unwieldy construct to grasp compared to if/else, plus the latter allows for multiple elif clauses as well.
Actually, mypy and Pycharm (did not test others) already deal with try/except/else: ```python def as_integer(x) -> int:... x = ... try: checked_x = as_integer(x) except: ... else: reveal_type(checked_x) # note: Revealed type is 'builtins.int' ``` But I completely agree that if/else is more readable and more flexible, notably with elif.
Note that it's also easy to implement your as_integer() using a type guard:
Indeed, and it's kind of logical as the point of my example was to illustrate a replacement for type guard, the opposite replacement should be easy :-) Currently, I'm using checked cast pattern in place of type guard in my code because this is the best way I've found so far.
That's not the problem the proposal is solving.
That's not *exactly* the problem the proposal is solving, it's rather a design pattern to get around and solve the problem: instead of refining the type of variable, assign (or fail) a new variable with the refined type and use this new variable. But you seemed not enclined to this pattern, and I'm fine with that— I'm also waiting this PEP to replace my checked cast by type guards (but I would also have accepted if it was rejected to use checked cast pattern instead).
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.
Fair enough. Seems my proposals are being rejected. Does the PEP need an update about them?
TS has been used as a motivation and justification for the term, and as such I think that we should not deviate that much from how type guards work on Typescript. Unfortunately there is no specification of user-defined typeguards, as TS stopped maintaining the language spec some years ago. However, they do specify what a type guard is (see section 4.2 of https://javascript.xgqfrms.xyz/pdfs/TypeScript%20Language%20Specification.pd...), so I guess the implemented behavior for user-defined type guards is compatible with that definition. Maybe there should be a prelude PEP norming how a type guard should be treated by a type checker. There are also some important differences between the two: - In TS, user defined typeguards are compatible with (...) => bool. However the proposed PEP says: *"TypeGuard is a distinct type from bool. It is not a subtype of bool. Therefore, Callable[..., TypeGuard[int]] is not assignable to Callable[..., bool]."*, this *prevents using typeguards as predicates for filtering functions*. - TS does apply narrowing in the negative branch. However, the proposal explicitly mandates not to narrow in such case.* "User-defined type guards apply narrowing only in the positive case (the if clause). The type is not narrowed in the negative case."* This is important to clarify, as then the semantics of the type guard would be different. Basically in TS a typeguard MUST return true whenever the condition holds, however in the python proposal returning true means you can prove the condition holds. I think we should require the former and let the type checker decide whether they want to narrow in the negative case. I imagine the narrowing logic for negative cases is already implemented as they currently do it for isinstance checks. - Not clear if type guards should be applied in and/or expressions: "When a conditional statement includes a call to a user-defined type guard function" - TS requires "compatible" narrowing. This was ruled out because that "require[s] that we articulate all of the subtle ways in which the rules differ and under what specific circumstances the constrains are relaxed" and "it is safe to assume that they are interested in type safety and will not write their type guard functions in a way that will undermine type safety or produce nonsensical results". However, the latter statement may be true initially, but after refactoring a type one could potentially end up with a non-sensical guard. - Also there's a possible "quirk" in how typeguards function in TS, which may be related to type narrowing. This is better explained by the following example: function isFoo(x: string | number): x is string { return true; } const x = 3; if (isFoo(x)) { const revealType : never = x } else { const revealType : never = x } In the positive branch x will have a type of never not string, which basically prevents using it in various contexts. See playground https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABDAzgMTnAFADwFyIpQB... On Sun, Feb 14, 2021 at 9:07 PM Joseph Perez <joperez@hotmail.fr> wrote:
The existing logic in type checkers does not deal with control flow due to try/except/else, because it's hard to reason about when an exception can or can't happen. Even if we could address this, to human readers try/except/else is an unwieldy construct to grasp compared to if/else, plus the latter allows for multiple elif clauses as well.
Actually, mypy and Pycharm (did not test others) already deal with try/except/else: ```python def as_integer(x) -> int:... x = ... try: checked_x = as_integer(x) except: ... else: reveal_type(checked_x) # note: Revealed type is 'builtins.int' ``` But I completely agree that if/else is more readable and more flexible, notably with elif.
Note that it's also easy to implement your as_integer() using a type guard:
Indeed, and it's kind of logical as the point of my example was to illustrate a replacement for type guard, the opposite replacement should be easy :-) Currently, I'm using checked cast pattern in place of type guard in my code because this is the best way I've found so far.
That's not the problem the proposal is solving.
That's not *exactly* the problem the proposal is solving, it's rather a design pattern to get around and solve the problem: instead of refining the type of variable, assign (or fail) a new variable with the refined type and use this new variable.
But you seemed not enclined to this pattern, and I'm fine with that— I'm also waiting this PEP to replace my checked cast by type guards (but I would also have accepted if it was rejected to use checked cast pattern instead).
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.
Fair enough.
Seems my proposals are being rejected. Does the PEP need an update about them? _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: skreft@gmail.com
-- Sebastian Kreft
In TS, user defined typeguards are compatible with (...) => bool. However the proposed PEP says: "TypeGuard is a distinct type from bool. It is not a subtype of bool. Therefore, Callable[..., TypeGuard[int]] is not assignable to Callable[..., bool].", this prevents using typeguards as predicates for filtering functions.
This surprises me, actually, as I thought it was intended to be compatible with uses of bool (at least in my mental model). One motivation I would have for making it compatible with bool is that existing functions could be converted to TypeGuard (e.g., inspect.isfunction). Otherwise, existing functions in the wild could not be typed with TypeGuard without forcing odd cases or type ignores... It makes sense why TS is bools; I have seen libs like is-promise get statically typed (even though the underlying code is not TS). It would surprise me to not be able to do the same in Python.
TS does apply narrowing in the negative branch. However, the proposal explicitly mandates not to narrow in such case.
FWIW, we have somewhat regular meetings with the TS folks (pyright's internals are very similar to TS's) and brought TypeGuard up, and I believe it was their impression that their type guards proving the negative in TS was a mistake, and often led to confusion and typings that had to be reverted as they couldn't actually prove the negative. I'm sure Eric knows the exact wording.
FWIW after reviewing the feedback on the PEP, I think now that we should proceed with the original proposal and submit it to the Steering Council's tracker (https://github.com/python/steering-council/issues) for their review. Eric, what do you think? --Guido On Tue, Feb 9, 2021 at 8:21 AM Guido van Rossum <guido@python.org> wrote:
I think we have reached consensus on PEP 647 in typing-sig. We have implementations for mypy and pyright, not sure about the rest. This PEP does not affect CPython directly except for the addition of one special item (TypeGuard) to typing.py -- it would be nice to get that in the 3.10 stdlib.
I'm CC'ing python-dev here to see if there are any further comments; if not, we can initiate the approval process by creating an issue at https://github.com/python/steering-council.
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
Yes, I agree that it's ready for submission to the Steering Council. -- Eric Traut Contributor to Pyright & Pylance Microsoft Corp.
Sorry to be so late to the conversation but can someone point me to a discussion of why `TypeGaurd[T]` is not a subtype of bool? It seems a bit strange to not be able to pass a type guard as a filter e.g.: ``` def filter(f: Callable[[object], bool], xs: Sequence[object]) -> Sequence[object]: ... def is_int(x: object) -> TypeGaurd[int]: ... filter(is_int, [...]) # error? ```
I found this confusing too, until I looked further into the overload rules and overlapping with classes. Consider this reduced example: ``` from typing import Union, overload class bool: ... class TypeGuard(bool): ... # Illegal, but pretend it is okay. @overload def func(x: TypeGuard) -> int: ... @overload def func(x: bool) -> str: ... def func(x: Union[TypeGuard, bool]) -> Union[int, str]: if isinstance(x, TypeGuard): return 1234 return "hey" ``` If I then write: ``` def do_something() -> bool: return TypeGuard() b: bool = do_something() x = func(b) ``` If we say "it's a `bool`" and attempt to pick the second overload, it will be wrong, because at runtime it's `TypeGuard`, and `func` returns an `int`, not the expected `str`. See also: https://github.com/python/typing/issues/253#issuecomment-389262904
I don't find this example particularly compelling. `TypeGaurd` is not an instantiable type. A function that returns `TypeGaurd[T]` in actuality returns `bool`: """ Return statements within a type guard function should return bool values, and type checkers should verify that all return paths return a bool. """ So this issue of overloading seems pretty irrelevant. On Wed, Mar 17, 2021 at 4:31 PM Jake Bailey via Typing-sig < typing-sig@python.org> wrote:
I found this confusing too, until I looked further into the overload rules and overlapping with classes. Consider this reduced example:
``` from typing import Union, overload
class bool: ... class TypeGuard(bool): ... # Illegal, but pretend it is okay.
@overload def func(x: TypeGuard) -> int: ... @overload def func(x: bool) -> str: ... def func(x: Union[TypeGuard, bool]) -> Union[int, str]: if isinstance(x, TypeGuard): return 1234 return "hey" ```
If I then write:
``` def do_something() -> bool: return TypeGuard()
b: bool = do_something() x = func(b) ```
If we say "it's a `bool`" and attempt to pick the second overload, it will be wrong, because at runtime it's `TypeGuard`, and `func` returns an `int`, not the expected `str`.
See also: https://github.com/python/typing/issues/253#issuecomment-389262904 _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: donovick@cs.stanford.edu
On 3/17/21, 5:31 PM, "Jake Bailey via Typing-sig" <typing-sig@python.org> wrote:
I found this confusing too, until I looked further into the overload rules and overlapping with classes. ... If we say "it's a `bool`" and attempt to pick the second overload, it will be wrong, because at runtime it's `TypeGuard`, and `func` returns an `int`, not the expected `str`.
This seems like an orthogonal issue with overloads (or with invalid assumptions made in that particular use of overload); what is specific to TypeGuard about it? The example code would exhibit the same behavior if you used two arbitrary classes `A` and `B(A)` instead of `bool` and `TypeGuard(bool)`. It doesn't seem like this example alone should be a reason for `Callable[..., TypeGuard]` to not be assignable to `Callable[..., bool]`. Since the inability to use type guards as filter predicates is likely to recur as an FAQ for users of TypeGuard, I would suggest that if there is a strong reason for it, the PEP should provide that reason, perhaps as an entry in the "Rejected Ideas" section. Currently as far as I can see the PEP provies no rationale for that choice. Carl
Nothing about it is specific to TypeGuard, except that if TypeGuard is "compatible with bool", then it overlaps, and we get this behavior. It means that we can't rewrite existing functions in terms of TypeGuard, because we can't tell when a TypeGuard has been assigned to a bool and then distinguish it later. We have this issue already thanks to int/float conversions. I agree that this is good to note in the PEP.
" because we can't tell when a TypeGuard has been assigned to a bool and then distinguish it later" Why is this not preferable to a type error? Yes I can destroy type information by assigning a subtype value to a supertype variable but what makes `TypeGaurd[T]` so special that we don't ever want to risk losing its runtime type? Assumably I can assign a `TypeGuard[T]` to an object variable, why is this not also a problem? On Wed, Mar 17, 2021 at 4:58 PM Jake Bailey via Typing-sig < typing-sig@python.org> wrote:
Nothing about it is specific to TypeGuard, except that if TypeGuard is "compatible with bool", then it overlaps, and we get this behavior. It means that we can't rewrite existing functions in terms of TypeGuard, because we can't tell when a TypeGuard has been assigned to a bool and then distinguish it later. We have this issue already thanks to int/float conversions.
I agree that this is good to note in the PEP. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: donovick@cs.stanford.edu
You are still not allowed to write: @overload def func(x: int) -> str: ... @overload def func(x: object) -> int: ... def func(x): ... https://mypy-play.net/?mypy=latest&python=3.9&gist=eacc907d13ee227c04fc177aa706ca2d You can't distinguish TypeGuard from bool in an overload if they have overlapping types but the signature is the same; the point is to change the return value based on the TypeGuard's TypeVar (so you can say, change filter), but if the overload rules don't allow it, then it becomes useless.
You can't distinguish TypeGuard and bool at run time (as all the runtime type of all TypeGuard variables shall be bool in a well type program) so overload containing both should be rejected. Your argument only strengthens my point. On Wed, Mar 17, 2021 at 5:24 PM Jake Bailey via Typing-sig < typing-sig@python.org> wrote:
You are still not allowed to write:
@overload def func(x: int) -> str: ... @overload def func(x: object) -> int: ... def func(x): ...
https://mypy-play.net/?mypy=latest&python=3.9&gist=eacc907d13ee227c04fc177aa706ca2d
You can't distinguish TypeGuard from bool in an overload if they have overlapping types but the signature is the same; the point is to change the return value based on the TypeGuard's TypeVar (so you can say, change filter), but if the overload rules don't allow it, then it becomes useless. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: donovick@cs.stanford.edu
Except then no existing function (like filter) could ever get a new overload with TypeGuard, because the compatibility would make it illegal, reducing it's usefulness.
Give me a concrete example of an overload you want to write but wouldn't be able to if `TypeGaurd` is considered a subtype of bool by type checkers. (again regardless of what type checkers think typeguard variables will be bools so `isinstance(guard, bool)` is going to be True and `isinstance(guard, TypeGuard)` is going to raise TypeError). On Wed, Mar 17, 2021 at 5:41 PM Jake Bailey via Typing-sig < typing-sig@python.org> wrote:
Except then no existing function (like filter) could ever get a new overload with TypeGuard, because the compatibility would make it illegal, reducing it's usefulness. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: donovick@cs.stanford.edu
The only use case I can come up with is something like: ``` @overload def filter(f: Callable[..., TypeGuard[int]], x: Sequence[object]) -> Sequence[int]: ... @overload def filter(f: Callable[..., bool], x: Sequence[object]) -> Sequence[object]: ... def filter(x): ... ``` Except that will work regardless of whether or not TypeGuard is a subtype of bool. https://mypy-play.net/?mypy=latest&python=3.9&gist=c918fdc74eeab7771f921666fe04f80c On Wed, Mar 17, 2021 at 5:47 PM Caleb Donovick <donovick@cs.stanford.edu> wrote:
Give me a concrete example of an overload you want to write but wouldn't be able to if `TypeGaurd` is considered a subtype of bool by type checkers. (again regardless of what type checkers think typeguard variables will be bools so `isinstance(guard, bool)` is going to be True and `isinstance(guard, TypeGuard)` is going to raise TypeError).
On Wed, Mar 17, 2021 at 5:41 PM Jake Bailey via Typing-sig < typing-sig@python.org> wrote:
Except then no existing function (like filter) could ever get a new overload with TypeGuard, because the compatibility would make it illegal, reducing it's usefulness. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: donovick@cs.stanford.edu
On 3/17/21, 6:42 PM, "Jake Bailey via Typing-sig" <typing-sig@python.org> wrote:
Except then no existing function (like filter) could ever get a new overload with TypeGuard, because the compatibility would make it illegal, reducing it's usefulness.
I'm afraid I still don't see how this is true. It would mean that adding a TypeGuard overload to a function that already has a bool overload would need to comply with the rule that the TypeGuard overload must come first, and must return a type that is less than the type returned by the bool overload. But that seems like everything working as desired. A typeguard is a form of bool that carries additional type information, so an overload involving a typeguard instead of a bool should be able to specify a narrower return type. I think it would help me understand a lot better if you can offer a specific real-world example of a desirable function overload that would become illegal if TypeGuard were a subtype of bool. Carl _______________________________________________ Typing-sig mailing list -- mailto:typing-sig@python.org To unsubscribe send an email to mailto:typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: mailto:carljm@instagram.com
If all TypeGuards are assignable to bool, but bool is not assignable to any TypeGuard, and at runtime are still always bool (so an "implicit subtype", as I've seen spelled out elsewhere), then the closest analog in any currently available system is int/float, since one can use an in where float is required and it's legal. I can't attach any type information in this way, but I'm trying to work with what I have. from typing import Any, Callable, overload Bool = float TypeGuard = int @overload def func(x: Callable[..., TypeGuard]) -> str: ... @overload def func(x: Callable[..., Bool]) -> int: ... def func(x: Any) -> Any: ... Pyright in strict mode reports: Overload 1 for "func" overlaps overload 2 and returns an incompatible type mypy says nothing, due to https://github.com/python/mypy/issues/10143. Perhaps that's some of the source as to why these examples are not working. I'm going to defer explaining this requirement to either Eric or Guido; it's clear my explanation of this based on what I got through the grapevine isn't holding up to a pile-on.
Sorry, I don't have the bandwidth to rehash this. One thing I recall is that the bool type is final at runtime, but that's not the only reason. On Thu, Mar 18, 2021 at 10:22 AM Jake Bailey via Typing-sig < typing-sig@python.org> wrote:
If all TypeGuards are assignable to bool, but bool is not assignable to any TypeGuard, and at runtime are still always bool (so an "implicit subtype", as I've seen spelled out elsewhere), then the closest analog in any currently available system is int/float, since one can use an in where float is required and it's legal. I can't attach any type information in this way, but I'm trying to work with what I have.
from typing import Any, Callable, overload
Bool = float TypeGuard = int
@overload def func(x: Callable[..., TypeGuard]) -> str: ... @overload def func(x: Callable[..., Bool]) -> int: ... def func(x: Any) -> Any: ...
Pyright in strict mode reports: Overload 1 for "func" overlaps overload 2 and returns an incompatible type
mypy says nothing, due to https://github.com/python/mypy/issues/10143. Perhaps that's some of the source as to why these examples are not working.
I'm going to defer explaining this requirement to either Eric or Guido; it's clear my explanation of this based on what I got through the grapevine isn't holding up to a pile-on. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: guido@python.org
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
Can you give me a pointer to a previous hashing? (Which is all I really wanted to begin with, it seems like something that must have been discussed previously). Thanks, Caleb On Thu, Mar 18, 2021, 11:29 AM Guido van Rossum <guido@python.org> wrote:
Sorry, I don't have the bandwidth to rehash this. One thing I recall is that the bool type is final at runtime, but that's not the only reason.
On Thu, Mar 18, 2021 at 10:22 AM Jake Bailey via Typing-sig < typing-sig@python.org> wrote:
If all TypeGuards are assignable to bool, but bool is not assignable to any TypeGuard, and at runtime are still always bool (so an "implicit subtype", as I've seen spelled out elsewhere), then the closest analog in any currently available system is int/float, since one can use an in where float is required and it's legal. I can't attach any type information in this way, but I'm trying to work with what I have.
from typing import Any, Callable, overload
Bool = float TypeGuard = int
@overload def func(x: Callable[..., TypeGuard]) -> str: ... @overload def func(x: Callable[..., Bool]) -> int: ... def func(x: Any) -> Any: ...
Pyright in strict mode reports: Overload 1 for "func" overlaps overload 2 and returns an incompatible type
mypy says nothing, due to https://github.com/python/mypy/issues/10143. Perhaps that's some of the source as to why these examples are not working.
I'm going to defer explaining this requirement to either Eric or Guido; it's clear my explanation of this based on what I got through the grapevine isn't holding up to a pile-on. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: guido@python.org
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/> _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: donovick@cs.stanford.edu
participants (7)
-
Caleb Donovick
-
Carl Meyer
-
Eric Traut
-
Guido van Rossum
-
Jake Bailey
-
Joseph Perez
-
Sebastian Kreft