-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Infer type from in
container check
#10977
Comments
More precisely, the type within an |
From the perspective of a type checker, the above code looks like the following: if m.__contains__(v):
m.__getitem__(v) Unless a type checker has intimate knowledge about the internal implementation of Generally, type checkers do not (and should not) assume knowledge about internal implementations of specific classes. They base their inferences only on the information provided in type declarations. Even if it were feasible to maintain hard-coded semantic knowledge about a subset of built-in classes, behaviors can be overridden by subclasses, and a type checker would have no way of knowing when its assumptions were invalidated. |
Why not?
As does
By that logic we shouldn't infer types from class A:
def __init__(self):
self.foo = 1
class B:
def __init__(self):
self.__class__ = A
b = B()
if isinstance(b, A):
print(b.foo) |
Consider a more concrete example: from collections.abc import Mapping
from typing import TypeVar, Union
A = TypeVar('A')
B = TypeVar('B')
class LookupTable(Mapping[A, B]):
def __setitem__(self, key: A, value: B) -> None: ...
def __getitem__(self, key: A) -> B: ...
def __contains__(self, key: A) -> bool: ...
table: LookupTable[str, float]
key: Union[str, bytes, float]
if key in table:
print(table[key]) I think the crux of the problem is here:
Even in a legitimate case, how do you determine whether to narrow the type to table: LookupTable[str, float]
key: None
if key in table:
print(table[key]) Perhaps Mypy could add some kind of That, or |
@gwerbin FWIW, Dreamsorcerer is talking about from typing import Container, Mapping, TypeVar
T = TypeVar('T')
def f(n: Container[T]) -> T: ...
blah: Mapping[str, float]
reveal_type(f(blah)) # N: Revealed type is "builtins.str*" https://mypy-play.net/?mypy=latest&python=3.10&gist=96d1eb453aca921853fe80e6876af4da ... I for one would love to see the effects this narrowing would have on mypy-primer. I wonder if it would have an overall good (bunch of this type of pattern) or bad (bunch of weird situations in libraries) effect :) |
Yes, as per the error message, this problem is inherited from @runtime_checkable
class Container(Protocol[_T_co]):
@abstractmethod
def __contains__(self, __x: object) -> bool: ... |
I'm still confused why we are talking about changing the type on The implementation should only return Essentially, the way it looks in my head is like this:
So, both of these should be able to achieve the same result: from typing import Container, Union
a: Union[str, bytes, int]
c: Container[Union[bytes, int, bool]]
if isinstance(a, (bytes, int, bool)):
reveal_type(a) # Revealed type is "Union[builtins.bytes, builtins.int]"
if a in c:
reveal_type(a) # Revealed type is "Union[builtins.str, builtins.bytes, builtins.int] Both could be inferred to be
That would definitely be the best idea to see if there really are a bunch of weird edge cases used in practice. |
Also worth pointing out that mypy already makes this assumption about implementation, so we are not actually changing any assumptions here: x: str
y: Container[int]
x in y # error: Non-overlapping container check (element type: "str", container item type: "int") If mypy is assuming that a type cannot be in the container, then why does it not carry that assumption to narrowing the type? |
I think what I was missing my example is that Perhaps the inference can be restricted to objects that explicitly inherit from class Weird:
def __contains__(self, key: object) -> bool:
return True
def __getitem__(self, value: int) -> int:
return value + 5
x: Union[str, bytes] = 'hello'
w = Weird()
if x in w:
# It would not make sense to infer anything about the type of x here.
result = w[x] |
I think that is implicit, You can see this already from the fact that mypy does not issue a 'Non-overlapping container check' error in your example, as it does in my previous comment. I don't expect it to attempt type narrowing in any situation that it can't already do an overlapping check. |
Yeah, I buy that line of reasoning. If a class explicitly subclasses from |
Also related, narrowing of the container should also work:
This should be able to infer the type as |
@Dreamsorcerer that example is unsafe because of invariance. |
Ah, good point. It would still be nice if it would recognise that values when iterating over the set would all be |
Sorry for hijacking this thread, but I'd expect that a class subclassing |
This means |
Or, more generally (because a str will never be in the container), a variable of |
This produces:
Invalid index type "Union[str, bytes]" for "Mapping[str, str]"; expected type "str" [index]
But, we just checked that the value is in the mapping; so firstly, it most certainly is a valid index, but secondly, we can also infer that the variable is in fact
str
type.The text was updated successfully, but these errors were encountered: