-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PEP 647 (TypeGuard) tracker #5406
Comments
@sproshev does PyCharm support TypeGuard already? |
Not even planned :( It is going to be released in Python 3.10, right? |
That's right. It's probably pretty simple to get enough support to allow TypeGuard in typeshed: just interpret |
Some examples of functions where this could be useful:
|
Unless I'm mistaken, |
(Why does the PEP say so?) |
I think there was some bad interplay with overload type overlapping that necessitated them being unique (e.g. conditions where a function overload couldn't have a more specific return in the case of a TypeGuard parameter). @gvanrossum or @erictraut may remember; I wasn't there for the discussion and I don't want to go too far into second-hand explanation past citing the PEP. |
Testing this, mypy and pyright seem to disagree. I wrote this code:
Mypy accepts this, but pyright gives errors on the
|
PEP 647 says:
I added this to the spec at your recommendation, Guido, because you argued (convincingly) that there were cases where we would want to distinguish between So it appears that mypy is not conforming to the PEP. |
Thanks for the explanation! It's very limiting though if we can't safely change existing functions like As a user I'd expect Guido's |
Unfortunately if we create any equivalency between bool and TypeGuard[T],
we'd lose the ability to use them as distinct in overloads. E.g.
```
@overload
def filter(f: Callable[[Any], TypeGuard[T]], Iterable[Any]) -> Iterable[T]:
...
@overload
def filter(f: Callable[[T], bool], Iterable[T]) -> Iterable[T]: ...
```
|
I think you can, just tried this with mypy: from typing import Any, Callable, overload
@overload
def f(x: Callable[[], bool]) -> str: ...
@overload
def f(x: Callable[[], int]) -> float: ...
def f(x: Any) -> Any: ...
def b() -> bool: return True
def i() -> int: return 0
reveal_type(f(b)) # Revealed type is 'builtins.str'
reveal_type(f(i)) # Revealed type is 'builtins.float' It does complain that the overloads overlap, but it infers the correct types. |
I tried to create some concrete examples to show this back in the "final call for comments" thread in relation to python/typing#253 (comment), and some seemed to think that this restriction was unnecessary, but I gave up thinking about it after getting berated enough to not want to continue participating in the thread anymore. |
If |
What does "this" refer to? The restriction on overloads being distinct? Or something related to TypeGuard?
Sorry for your experience. We should do better. |
But that's the problem, right? There's a reason for the complaint:
|
Both, together (if I'm understanding correctly). https://mail.python.org/archives/list/typing-sig@python.org/message/KFGHKN32LCWKHIDGLJIUUP2E67MMDARR/ Picking int and float, as ints are assignable to floats but not the other way around (which is how I would mentally expect TypeGuard to operate with relation to bool), and an esoteric mismatch in return type (versus trying to construct something with Iterable and such): from typing import Any, Callable, overload
Bool = float
TypeGuard = int
@overload
def func(x: Callable[..., TypeGuard]) -> str: ...
@overload
def func(x: Callable[..., Bool]) -> int: ... pyright errors, mypy doesn't due to python/mypy#10143. Then depending on how you obtain But, I'm willing to be wrong about this. I'd certainly like if existing functions could become type guards (like they did in TS). |
I agree that mypy should error on that example. (@JelleZijlstra's example does complain, because there the subclass relationship is explicit -- FWIW the issue is presumably limited to overloading on types involving |
Yeah, in my case, I was trying to come up with two types that were close to bool and TypeGuard, since the latter doesn't actually exist at runtime and I'd expect the relationship to also be an implicit subtype for purposes of type annotations only. I can't off the top of my head think of another pairing like it, besides maybe TypedDict. |
Ehh, that's weird. IMO,
I have no desire to implement that in mypy though, we don't support this for things like |
Hm, then what's the point of inferring TypeGuard instead of bool?
|
pyright doesn't actually support Guido's example, but it does infer Stepping back, the reason I'm pushing for a change here is that it would be unfortunate if we can't take advantage of TypeGuard on any existing functions. Users who want narrowing from functions like I still think that making |
Of course, if TypeGuard were compatible in some sense, then how this is chosen wouldn't really matter, except maybe that variables may be inferred to have a type that's more specific than what a user wants, potentially (but that's nothing new). |
I guess in our minds we think of this fundamentally differently. To me, If you consider the TypeScript equivalent, In Python, |
Just catching up on this thread... I confirmed that there was a bug in pyright's implementation when evaluating the return type of a call to a user-defined type guard. It should evaluate the return type as The question that is being raised here is whether a function that accepts a callback of type |
Thanks Eric, that's great to hear! Then I think the only thing blocking us from using TypeGuard in typeshed is PyCharm. |
I think PyCharm should mostly be considered a "soft blocker". While we shouldn't break central, often used functions, adding |
I just noticed that there's still a small difference between the behavior of mypy and pyright. Mypy apparently allows non-type-guard functions (those that return a def notguard(x: Any) -> bool: ...
def hof2(func: Callable[[Any], TypeGuard[SomeType]]): ...
hof2(notguard) # Pyright flags this as an error, mypy does not @gvanrossum, can you explain why you think this should not be an error? Is there a compelling reason to support this? I think it creates some problems, specifically for overload matching. It also creates problems for generics used in TypeGuards. For example, what would you expect the return result to be for the following call? def my_filter(func: Callable[[Any], TypeGuard[T]]) -> List[T]: ...
my_filter(notguard) # What is the return type of this call? List[Any]? Maybe it's OK for mypy and pyright to differ in this specific use case, but I want to make you all aware of this because pyright's current behavior (with my recent changes noted above) requires that any use of TypeGuard callbacks in typeshed must be done with overloads, and the TypeGuard overload must appear prior to the non-TypeGuard overload, like this: @overload
def my_filter(func: Callable[[Any], TypeGuard[T]]) -> List[T]: ...
@overload
def my_filter(func: Callable[[Any], bool) -> List[Any]: ... |
- Use TypeGuard for various is* functions (refer to python#5406) - Use collections.abc and builtin containers
I wasn’t expressing an opinion on what is correct, just observing a difference. I don’t think I will get to changing mypy even if it is decided it’s wrong, but someone else might. Sorry! |
I agree that the mypy behavior Eric highlighted doesn't make sense, so I opened an issue about it. With Sebastian's comment on PyCharm, I think we're OK to start using TypeGuard in typeshed, so I'm closing this issue. Thanks everyone for the work on TypeGuard! |
Bumped pyright's locked version to 1.1.140, which includes the aforementioned fixes. Happy to know that the difference was a bug; I probably should have known better when I went to TS, wrote the same type guard, and the hover said |
- Use TypeGuard for various is* functions (refer to #5406) - Use collections.abc and builtin containers
I'd like to start using TypeGuard in typeshed. Here's what we need:
The text was updated successfully, but these errors were encountered: