如何编写一个满足typing.TextIO的类文件类?

Posted

技术标签:

【中文标题】如何编写一个满足typing.TextIO的类文件类?【英文标题】:How to write a file-like class that satisfies typing.TextIO? 【发布时间】:2021-12-02 19:10:54 【问题描述】:

在编写实现类文件接口的类时,我们可以从io 模块继承其中一个抽象基类,例如TextIOBase,如Adapt an iterator to behave like a file-like object in Python 所示。

另一方面,在类型注释中,我们应该使用派生自typing.IO 的类(例如TextIO)来表示此类对象,如Type hint for a file or file-like object? 或Type-checking issue with io.TextIOBase in a Union 所示。

但是,这似乎不像我预期的那样工作:

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

在这段代码上运行 mypy 时(使用 Python 3.7.3 和 mypy 0.910),我们得到

错误:“hello”的参数 1 具有不兼容的类型“MyIO”;预期的“TextIO”

问题

如何编写MyIO 类,使其被接受为typing.TextIO 类型的函数参数(而不仅仅是使用typing.cast(typing.TextIO, ...))?

尝试失败

    使用typing.TextIO 作为基类是不可能的:

    使用class MyIO(typing.TextIO)时:

    错误:无法使用抽象属性“__enter__”、“__exit__”、...和“writelines”实例化抽象类“MyIO”(抑制了 15 个方法)

    使用class MyIO(io.TextIOBase, typing.TextIO):时:

    错误:基类“IOBase”中“readlines”的定义与基类“IO”中的定义不兼容

    其他几种方法也是如此。

    覆盖__new__ 并将typing.TextIO 注释为返回类型不起作用:

    def __new__(cls, *args, **kwargs) -> typing.TextIO:                        
        return super().__new__(cls, *args, **kwargs)
    

    结果

    错误:“__new__”的返回类型不兼容(返回“TextIO”,但必须返回“MyIO”的子类型) 错误:返回值类型不兼容(得到“MyIO”,预期为“TextIO”)

或者这已经可以工作了,而我使用的 Python 和/或 mypy 版本太旧了?但是,使用 --python-version 3.83.93.10 作为 mypy 的选项不会改变任何内容。

【问题讨论】:

我正在使用 Python 3.9 和 pyright。排版中的io.pyi 有TextIOWrapper 继承自typing.TextIO,但TextIOBase 不这样做。 reveal_type(open('temp.txt', 'w')) 为我显示 TextIOWrapper。继承class MyIO(io.TextIOBase, typing.TextIO) 对我有用,pyright 不会抱怨。当然TextIOBase 应该继承自typing.TextIO 然后在类型库中。 看起来 MyPy (0.910) 有一个问题,即 io 方法在 typeshed 中被覆盖的奇怪方式。由于TextIOBase 继承自IOBase,MyPy 例如期望 both __iter__: () -> Iterator[str]__iter__: () -> Iterator[bytes] 【参考方案1】:

改用io.StringIO

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
 

【讨论】:

【参考方案2】:

我认为您的第一次尝试实际上是正确的,它只需要您实现所有抽象方法(如错误所示)。你不必把实际的逻辑放在那里。以下课程可以解决问题:

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

【讨论】:

以上是关于如何编写一个满足typing.TextIO的类文件类?的主要内容,如果未能解决你的问题,请参考以下文章

编写一个表示二维平面上的点的类MyPoint,满足以下条件: 1定义private的成员变量x和y,表示点的x和y坐标,类型为double

如何从 C# 访问 C++/CLI 中的类?

面试题思考:如何编写自己的类加载器

如何将在VC6.0下编写的类文件添加到QT工程里使用?

我如何以及为啥要编写一个扩展 null 的类?

如何在 Python 中编写有效的类装饰器?