Reputation: 23246
When writing a class that implements a file-like interface we can inherit one of the abstract base classes from the io
module, for example TextIOBase
, as shown in Adapt an iterator to behave like a file-like object in Python.
On the other hand, in type annotations we are supposed to use the classes derived from typing.IO
(e.g. TextIO
) to represent such objects, as shown in Type hint for a file or file-like object? or Type-checking issue with io.TextIOBase in a Union.
However, this does not seem to work as I expected:
import io
import sys
import typing
class MyIO(io.TextIOBase):
def write(self, text: str):
pass
def hello(f: typing.TextIO):
f.write('hello')
hello(sys.stdout) # type checks
hello(open('temp.txt', 'w')) # type checks
hello(MyIO()) # does not type check
When running mypy on this code (using Python 3.7.3 and mypy 0.910), we get
error: Argument 1 to "hello" has incompatible type "MyIO"; expected "TextIO"
How can the MyIO
class be written such that it is accepted as a function argument of type typing.TextIO
(without just using typing.cast(typing.TextIO, ...)
)?
Using typing.TextIO
as a base class is not possible:
When using class MyIO(typing.TextIO)
:
error: Cannot instantiate abstract class "MyIO" with abstract attributes "__enter__", "__exit__", ... and "writelines" (15 methods suppressed)
When using class MyIO(io.TextIOBase, typing.TextIO):
:
error: Definition of "readlines" in base class "IOBase" is incompatible with definition in base class "IO"
and the same for several other methods.
Overriding __new__
and annotating typing.TextIO
as return type does not work:
def __new__(cls, *args, **kwargs) -> typing.TextIO:
return super().__new__(cls, *args, **kwargs)
results in
error: Incompatible return type for "__new__" (returns "TextIO", but must return a subtype of "MyIO")
error: Incompatible return value type (got "MyIO", expected "TextIO")
Or is this already supposed to work, and I'm using too old a version of Python and/or mypy? Using --python-version 3.8
or 3.9
or 3.10
as option for mypy does not change anything, however.
Upvotes: 13
Views: 10351
Reputation: 1736
I think your first attempt is actually correct, it just requires you to implement all the abstract methods (as the error says). You dont have to put actual logic there. The following class would do the trick:
import io
from types import TracebackType
from typing import Optional, Type, Iterator, AnyStr, Iterable, TextIO
import sys
import typing
class MyIO(typing.TextIO):
def __enter__(self) -> TextIO:
pass
def close(self) -> None:
pass
def fileno(self) -> int:
pass
def flush(self) -> None:
pass
def isatty(self) -> bool:
pass
def read(self, n: int = ...) -> AnyStr:
pass
def readable(self) -> bool:
pass
def readline(self, limit: int = ...) -> AnyStr:
pass
def readlines(self, hint: int = ...) -> typing.List[AnyStr]:
pass
def seek(self, offset: int, whence: int = ...) -> int:
pass
def seekable(self) -> bool:
pass
def tell(self) -> int:
pass
def truncate(self, size: Optional[int] = ...) -> int:
pass
def writable(self) -> bool:
pass
def writelines(self, lines: Iterable[AnyStr]) -> None:
pass
def __next__(self) -> AnyStr:
pass
def __iter__(self) -> Iterator[AnyStr]:
pass
def __exit__(self, t: Optional[Type[BaseException]], value: Optional[BaseException],
traceback: Optional[TracebackType]) -> Optional[bool]:
pass
def write(self, text: str):
pass
def hello(f: typing.TextIO):
f.write('hello')
hello(sys.stdout) # type checks
hello(open('temp.txt', 'w')) # type checks
hello(MyIO()) # does not type check
Upvotes: 4
Reputation: 1847
Use io.StringIO
instead
import io
import sys
import typing
class MyIO(io.StringIO):
def write(self, text: str):
pass
def hello(f: typing.TextIO):
f.write("hello")
hello(sys.stdout) # type checks
hello(open("temp.txt", "w")) # type checks
hello(MyIO()) # type checks
Upvotes: 2