I have the following list comprehensions in Python:
from typing import cast
# everything is fine
print([value for value in [1, 2, 3, 4]])
# on the first "value": Expression type contains "Any" (has type "List[Any]")
print("{}".format([value for value in [1, 2, 3, 4]]))
# on the "cast": Expression type contains "Any" (has type "List[Any]")
print("{}".format([cast(int, value) for value in [1, 2, 3, 4]]))
Why does using format
cause Mypy to give me back errors? As you can see, I tried to use casting and it still failed.
This question looks similar, but my particular case is weird because Mypy seems to be fine as long as I'm not using the format
function (yet it's always okay with the print
function).
Is there anything I can do to not have the lines with formatting give me errors? (Or should I just # type: ignore
them?)
EDIT:
Note that this does not appear to just be an issue with my Atom linter.
I'm using Mypy version 0.701
and I ran Mypy on the file with the following result:
$ python3 -m mypy testing_list_iter.py --disallow-any-expr
testing_list_iter.py:7: error: Expression type contains "Any" (has type "List[Any]")
testing_list_iter.py:10: error: Expression type contains "Any" (has type "List[Any]")
This actually has nothing to do with list comprehensions: this is actually a bad interaction between the type signature for str.format(...)
, how mypy performs type inference, and the --disallow-any-expr
flag.
Here's the type signature for str.format(...)
, pulled from typeshed:
def format(self, *args: Any, **kwargs: Any) -> str: ...
When mypy performs type inference on function calls, it'll attempt to use the declared parameter types to help provide context for the expressions you pass in.
So in this case, since the arguments are all Any
here, mypy will realize that it can shortcut a lot of the type inference it usually needs to do. So, if we pass in any list literal into str.format(...)
, mypy will just decide "hey, the inferred type can be just List[Any]
".
Here's a sample program that demonstrates this behavior (when checked with the --disallow-any-expr
flag):
from typing import cast, Any
def test1(x: Any) -> None:
pass
def test2(x: object) -> None:
pass
# Revealed type is 'builtins.list[Any]'
# Expression type contains "Any" (has type "List[Any]")
test1(reveal_type([1, 2, 3, 4]))
# Revealed type is 'builtins.list[builtins.int*]'
test2(reveal_type([1, 2, 3, 4]))
Note that when we try using a function that accepts object
instead of Any
, mypy will infer the full type instead of doing this shortcut. (Mypy could technically do the same kind of shortcut, since all types also subclass object
, but I suspect it was just simpler implementation-wise not to -- unlike Any
, object
is just a regular plain-old type so special-casing interactions with it is kind of weird.)
Normally, it doesn't matter too much how exactly mypy handles this case: you get accurate results either way.
However, the --disallow-any-expr
flag is still pretty new and relatively untested (it's too aggressive for a lot of people, especially those who are trying to use mypy on existing codebases), so we get these bad interactions from time to time.
So, what's the fix?
The best possible fix would be for you to contribute a pull request to Typeshed modifying str.format(...)
and unicode.format(...)
in the builtins.pyi file so they accept objects instead of Any
.
This change would be in line with Typeshed's contribution guidelines anyways -- specifically, this snippet in the middle of the "Conventions" section:
When adding type hints, avoid using the
Any
type when possible. Reserve the use ofAny
for when:
- the correct type cannot be expressed in the current type system; and
- to avoid Union returns (see above).
Note that
Any
is not the correct type to use if you want to indicate that some function can accept literally anything: in those cases useobject
instead.
Then, you wait for the next release of mypy, which theoretically ought to be soon-ish.
In the meantime, what you can do is just assign the results of your list comprehension to a new variable, then pass that into str.format(...)
:
results = [value for value in [1, 2, 3, 4]]
print("{}".format(results))
This will cause mypy to infer the type of your list comprehension without the Any
context, causing it to infer the full-fledged type. This sidesteps the bad interaction with the --disallow-any-expr
flag.