I have a 'smart' open function that opens a variety of files and returns an IO-ish object type:
def sopen(anything_at_all: str, mode: str) -> FileIO:
...
And I use it in a print statement like:
with sopen('footxt.gz', mode = 'w ') as fout:
print("hello, world!", file=fout)
Then, when analyzing this code with mypy
0.812, I get the following mystery error:
Argument "fout" to "print" has incompatible type "FileIO"; expected "Optional[SupportsWrite[str]]"
Ok great: SupportsWrite
is definitely better than FileIO
, only one problem: when I adapt my code to support write using _typeshed.SupportsWrite
, nothing gets better...
def sopen(anything_at_all: str, mode: str) \
-> Union[SupportsWrite[str],SupportsRead[str]]:
...
mypy
wants exactly Optional[SupportsWrite]
:
Argument "fout" to "print" has incompatible type "Union[SupportsWrite[str], SupportsRead[str]]"; expected "Optional[SupportsWrite[str]]"
Next I try casting, and creating some sort of type-casting enforcement, but in the middle of trying out my caster in the interpreter to see what errors fall out when:
>>> from _typeshed import SupportsRead, SupportsWrite
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named '_typeshed'
And now the fundamental problem is: how does one comply, in this situation, with mypy
's wishes?
CodePudding user response:
TL;DR Use typing.IO
instead of FileIO
. typing.IO
supports all the return types that the built-in open
might return.
print
itself annotates its file
argument as Optional[SupportsWrite[str]]
, so mypy
is correct.
To fix the missing _typeshed
module error (also correct, it is only available when type checking, not when the interpreter is executing code) you can use the if TYPE_CHECKING
1 trick and then use string annotations.
The below almost satisfies mypy
:
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from _typeshed import SupportsWrite
def sopen(anything_at_all: str, mode: str) -> 'Optional[SupportsWrite[str]]':
...
with sopen('footxt.gz', mode = 'w ') as fout:
print("hello, world!", file=fout)
Feeding this to mypy
results with
test.py:9: error: Item "SupportsWrite[str]" of "Optional[SupportsWrite[str]]" has no attribute "__enter__"
test.py:9: error: Item "None" of "Optional[SupportsWrite[str]]" has no attribute "__enter__"
test.py:9: error: Item "SupportsWrite[str]" of "Optional[SupportsWrite[str]]" has no attribute "__exit__"
test.py:9: error: Item "None" of "Optional[SupportsWrite[str]]" has no attribute "__exit__"
Enters typing.IO
.
Instead of messing with SupportsWrite
directly, you can simply use typing.IO
(which also happens to match open
's return types). The following fully satisfies mypy
:
from typing import IO
def sopen(anything_at_all: str, mode: str) -> IO:
...
with sopen('footxt.gz', mode = 'w ') as fout:
print("hello, world!", file=fout)
1 TYPE_CHECKING
is a constant which is False
by default, and is only being set to True
by mypy
and/or other type analyzing tools.