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?
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_CHECKING1 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With