pythonpython-typingmypy

Type-checking issue with io.TextIOBase in a Union


In the following code, I have a function that can take either a string, or some TextIOBase. If a string is passed, it is interpreted as a path to a file that should be opened and read. If a TextIOBase is passed, the contents of that stream will be read:

from typing import Union
import io

def function(foo: Union[str, io.TextIOBase]) -> None:
    if isinstance(foo, str):
        foo = open(foo, "w")

This seems like it should be okay, because Unions are supposed to be covariant, meaning that a subclass of one of the types in the union should satisfy the type annotation, and in this case the output type of open() is a subclass of TextIOBase. However, mypy complains with:

union.py:6: error: Incompatible types in assignment (expression has type "TextIO", variable has type "Union[str, TextIOBase]")
Found 1 error in 1 file (checked 1 source file)

I figured maybe there is an issue with the ambiguity of the return type of open(), based on the passed arguments, so I tried making a StringIO instead, but got the same error. Any thoughts? Why is mypy mad at me?

I have also tried this with some toy classes (e.g. Union[str, T1], then assigning a T2, where T2 is a subclass of T1), which mypy is perfectly happy with.


Solution

  • The typing module has a dedicated object for that: typing.TextIO. The return type of open is determined based on the mode argument and evaluates as one of these types: TextIO, BinaryIO.