如何对 csv.writer 返回的对象进行类型注释?

How to type-annotate object returned by csv.writer?

我想将类型注释应用于 csv.writer 的 returned 对象,以符合更大的代码库。不幸的是,我无法找出合适的 return 类型。

>>> import csv
>>> writer = csv.writer(open('outfile.csv', 'w'))
>>> type(writer)
<class '_csv.writer'>

如果我尝试使用这个类名:

>>> import _csv
>>> writer: _csv.writer = csv.writer(open('outfile.csv', 'w'))

我收到以下 mypy 错误:

Invalid type "_csv.writer"

有人知道在这种情况下使用哪种类型吗?当然我可以使用 typing.Any 但这会使类型注释的意义无效。

简短的回答是无法直接访问类型。读取在 Python 中实现 _csv 模块的 C source of the _csv module will show that the types of reader and writer are not exposed. Even in Pypy,不会公开类型。

因此,如果您需要使用它,则需要通过实例化 writer 的临时实例并获取其类型来使用变通方法。

import csv
# We'll need a temporary file-like object, so use a tempfile
from tempfile import TemporaryFile

with TemporaryFile() as t:
    CSVReader = type(csv.reader(t))
    CSVWriter = type(csv.writer(t))

w: CSVWriter = csv.writer('path/to/data.csv')

如果您想将此逻辑分开,我建议在单独的模块中创建类型

from csv_types import CSVReader, CSVWriter

另一个解决方案(也涉及编写您自己的类型模块)是在 iore 类型定义中遵循 typing 模块的示例.

一个解决方案是写一个代表类型的抽象class。这也是 typing module. For the csv.writer() 函数中某些 classes 的处理方式,如下所示:

class _CSVWriter:

    @abstractmethod
    def writerow(self, row: List[str]) -> None:
        pass

    @abstractmethod
    def writerows(self, rows: List[List[str]]) -> None:
        pass

    @abstractproperty
    def dialect(self) -> csv.Dialect:
        pass

现在这个 class 可以用在 writer 对象的类型注释中。由于返回对象的类型名称仍然是 _csv.writer,您仍然会收到类型错误。为避免这种情况,您需要将其转换为 _CSVWriter 对象。

from typing import cast
writer: _CSVWriter = cast(_CSVWriter, csv.writer(open('test', 'w'))

该解决方案有点冗长,但可以完成工作。

通常情况下,当事情表现得很奇怪时,这是一个符号类型没有完全映射到运行时。如果您查看 _csv in typeshed,您会看到该类型被命名为 _writer。所以你应该可以注释 _csv._writer.

我 运行 遇到了 typeshed defs 的问题并最终使用了以下内容:

class Writer(Protocol):
    def writerow(self, row: Iterable[Any]) -> Any:
        ...

    def writerows(self, rows: Iterable[Iterable[Any]]) -> None:
        ...


Reader = Iterator[Any]
writer: Writer = csv.writer(open('outfile.csv', 'w'))
reader: Reader = csv.writer(open('outfile.csv', 'w'))

我看到原来的 class 继承自 Iterator 所以我能够做到:

from typing import Iterator

with open(spec_file) as csv_file:
            spec_csv: Iterator = csv.reader(csv_file)

然后 mypy 瞧:

Success: no issues found in 1 source file