-
-
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
Mypy errors with variable reuse with different types #1174
Comments
I think this is a duplicate of a common theme: variable reuse within one function body. Mypy really prefers that you don't do that. (I can't find a specific issue to point to though.) |
Yeah, this one has been discussed many times though there doesn't seem to be a github issue for this. I'm not really sure about what's the best way to approach this. Perhaps mypy should allow redefining a variable with a different type by default and provide a strict mode or option which disallows this. A general solution would be to logically define a fresh variable each time an assignment redefines a variable on all possible code paths. This would define a new variable, since the old definition can't reach beyond the second assignment:
This would not define a new variable:
Not sure about his:
A less general approach would be to only allow redefining with a new type only if the original definition didn't have a type annotation and the new type definition doesn't have a type annotation. So this would be okay:
But this would not be okay because the annotation is lying:
And this would not be okay:
|
For what it's worth, pytype always allows you do redefine a variable with a different type. We're essentially doing SSA. On the other hand, that also means that pytype considers code like this correct:
I know it looks odd, but allowing this pattern is is quite useful when adding function annotations to existing code. |
I propose moving this to an earlier milestone such as 0.4.0, at least tentatively, since issues like these a little painful to refactor, and they can generate a lot of noise. This is more of a style issue than correctness issue, and I'd rather not make mypy very opinionated about style issues. An optional flag that causes mypy to complain about questionable redefinitions would be better in my opinion. |
Have we run into this issue a lot internally? It looks to me like this'll be a lot of work -- we'd have to start doing a significant amount of control flow analysis, and I expect doing that properly will be somewhat difficult. |
I've hit this in some open source code I've experimented with. If we run
|
When you're checking this out, I'd recommend at least also checking the tests directories for the projects; my experience was that our test code had a much higher ratio of these than the rest of the codebase. |
Also just FYI |
It's --check-untyped-defs now. I ran this over a small corpus (under 150K LOC) and found, among a total of over 1200 errors, about 30 occurrences of this message (though 9 of these didn't have the A bunch (indeed most common in tests) were unrelated types, but in many cases arguably the error was due to a too narrowly inferred initial type. E.g.
A bunch were similar but the two types involved were different subclasses of the same superclass. Common was also assignment of differently sized tuples to a variable, e.g.
I've also seen things like
Other complicating factors were that a few times the assignments occurred in except clauses or other "blocks" where the variable wasn't used after exiting the blocks. All in all I do think this is a popular idiom that we ought to support better. |
Perhaps we could start with special casing some common idioms instead of trying to support all of the possible idioms. Here my concern is that the rules that mypy follows should be easy to describe and predictable, and approaches like SSA wouldn't really fit this description. Here are some idioms that would be easy enough to support and that I think are pretty common. 1) Redefined in the same block Examples:
Variables within for, while and try (what about with?) statements would be harder as an intermediate value may escape. Here nested blocks would be considered separate blocks, and we consider function arguments to be defined in the same block as the function body. 2) Variable never read outside statement that defines it Examples:
Also 'with'. For try/except this might already work. For module-level variables we'd take the last definition as the type of the variable, but if there is a conditional definition things get harder. 3) Conditional definitions don't agree on type This is actually a separate issue as here we have to infer a single type for a variable. Example (from above):
|
Sounds like a common theme is that if variables are defined and redefined at the same indentation level ("in the same block") and there's no local flow control in between we should check the code between the definitions using the first definition and after that use the second (last) definition. E.g.
Should work because the ultimate type in each block is the same. However this is questionable:
In a try block everything is questionable:
A future improvement could not care if there's no use afterwards. For branches that don't agree we could have some kind of unification (like I want for conditional expressions e.g. #1094). |
Moved to 0.5 (in some generality). |
@rhettinger showed me an example which redefines a def f() -> None:
items: Set[int] = set()
# populate s with values
items: FrozenSet[int] = frozenset(items)
# do stuff with items; we want to make sure it doesn't get mutated here |
Basic implementation was added in #6197, behind a flag. At the moment only simple cases where the redefinition happens in the same block as the original definition, and at the same nesting level, are supported. I'll create follow-up issues to make this more general. I'd still like to enable this by default at some point. |
Reusing variables can cause a mypy type error (python/mypy#1174). This previously went unnoticed because the previous and current use of `key` was `str`, but in #23644 the previous use was retyped to `Text` and as such caused a unicode-vs-str type error in mypy.
…, a=testonly Automatic update from web-platform-tests Rename variable to avoid mypy type error (#23669) Reusing variables can cause a mypy type error (python/mypy#1174). This previously went unnoticed because the previous and current use of `key` was `str`, but in web-platform-tests/wpt#23644 the previous use was retyped to `Text` and as such caused a unicode-vs-str type error in mypy. -- wpt-commits: 7b9a66f8a9cf68bbe220473f9eff0e652998c072 wpt-pr: 23669
…, a=testonly Automatic update from web-platform-tests Rename variable to avoid mypy type error (#23669) Reusing variables can cause a mypy type error (python/mypy#1174). This previously went unnoticed because the previous and current use of `key` was `str`, but in web-platform-tests/wpt#23644 the previous use was retyped to `Text` and as such caused a unicode-vs-str type error in mypy. -- wpt-commits: 7b9a66f8a9cf68bbe220473f9eff0e652998c072 wpt-pr: 23669
Is there any solution? I am using mypy 0.770, I am porting python2 code where I have hundred cases like the one below. I cannot afford giving new name to every variable - it will introduce a lot of changes that will be really hard to verify. I am ok with adding code or "# type: " directive at the "Point X". I tried using 'del val' it did not work. All I want is to forget the type of this variable at the point x (I want to keep type information in between though). And the 'val' could be a class.
|
Did not help: |
From documentation: Only redefinitions within the same block and nesting depth as the original definition are allowed. Your |
I understand this but unfortunately there is a lot of python2 code to be ported to python3 and it is written in this style. |
* Updated `Edge.makeSpline()` to use different temporary variables in the several for loops in the function body. Python for [loop target variables leak](https://eli.thegreenplace.net/2015/the-scope-of-index-variables-in-pythons-for-loops/) to the function (or enclosing module) scope, and MyPy doesn't accept [reusing the same variable name with a different type](python/mypy#1174) (except in specific cases, and when `--allow-redefinition` is used).
Mypy doesn't allow redefinition of a variable using a different type within the same scope. python/mypy#1174
Mypy doesn't allow redefinition of a variable using a different type within the same scope. python/mypy#1174
Mypy doesn't allow redefinition of a variable using a different type within the same scope. python/mypy#1174
Mypy doesn't allow redefinition of a variable using a different type within the same scope. python/mypy#1174
Mypy just doesn't like this when it's done in the local scope (see python/mypy#1174).
It's about "variable reuse with different types". Here's one of the issues: python/mypy#1174
Another example of where try:
# fetch list of numbers (as strings) from somewhere,
# like argv, an external command's output, an HTTP request, etc.
x: List[str] = ...
except ...:
# handle fetch error
...
else:
# convert to actual ints when fetched successfully
x: List[int] = list(map(int, x)) |
Mypy really doesn't like redefinitions¹, and we start with List[str] but then convert to List[int]. The limited support for redefinition (--allow-redefinition) doesn't apply here because two different code blocks are involved. This particular shift of code into the try block doesn't impact the handling of exceptions. ¹ python/mypy#1174
Adding a comment here despite being closed because #2806 refers here, and #6263 (which is open) could also be addressed, plus I think there's an opportunity to improve lots of code. @alex4747-pub 's comment above could also be resolved. No idea how much work it would be, but why not treat Examples of how this is useful: One common issue in this thread and other bugs:
MyPy complains about redefinition of
These dels ensure at runtime that nobody will accidentally use the loop variable afterwards (and if someone wanted to do so intentionally they could just leave off the del). This would also somewhat satisfy the wish for scoping (in a Pythonic way) expressed here: So if mypy matched the semantics of My actual use case is conditionals in a loop; I'm doing parsing and multiple branches want to use the same variable names (with the SAME types) but declaring the type locally is not allowed in the second instance. A toy example:
Here mypy complains that the second ": str "is a redefinition, which it is, and I can get it to be quiet if I just remove the type hint. But when the two cases are dozens of lines of code apart, I'd really like to have the second type hint, because conceptually they're different variables. And in fact, the situation is smelly since the variables can leak, and would be improved as:
With this new code, I've explicitly tied down the variables that should be scoped to individual blocks. I won't run into errors where I accidentally use 'name' without defining it somewhere else, and instead of a warning from my linter I end up using a value from one of these inner cases. But even with the If I should put this in as a separate feature request I'm happy to re-file it. Note despite what is written here: Python at runtime gives a One more note: mypy behaves strangely for this program:
It both complains about redefinition, and claims the print is an attempt to access a deleted variable. If run with |
gives
Arguably this is not perfect code, but probably this shouldn't be a mypy error?
The text was updated successfully, but these errors were encountered: