我有以下两个课程:
@runtime_checkable
class AbstractFolder(Protocol):
def __iter__(self) -> "AbstractFolder":
raise NotImplementedError
def __next__(self) -> AbstractFileReadable:
raise NotImplementedError
及其实施:
class FileSystemFolder(AbstractFolder, Iterator):
def __init__(self, path: str):
self.path: str = path
def __iter__(self) -> "FileSystemFolder":
self.jobs: List[AbstractFileReadable] = [
FileSystemFileReadable(path)
for path in [*glob(os.path.join(self.path, "*"))]
]
self.current: int = -1
return self
def __next__(self) -> FileSystemFileReadable:
self.current += 1
if self.current >= len(self.jobs):
raise StopIteration
return self.jobs[self.current]
和以下功能
def process(folder: AbstractFolder) -> None:
...
孩子们正在返回实现的实例,这可能很多,但是当我执行 mypy 时,我得到以下错误:
error: Incompatible return value type (got "AbstractFileReadable", expected "FileSystemFileReadable")
这是实施和使用协议和打字的正确方法吗?
谢谢
mypy
检测到的打字错误与您使用Protocol
无关。相反,它是 self.jobs
的类型(在 __iter__
中定义)和 __next__
的返回值之间的冲突。
尽管代码仅将
FileSystemFileReadable
的实例加载到 self.jobs
列表中,但其类型被暗示为 List[AbstractFileReadable]
,因此从理论上讲,其他一些代码可以在不违反类型声明的情况下向其添加不同类型的对象。当您从列表中取出值时,在 __next__
中,您将返回它们,并且返回类型被注释为 FileSystemFileReadable
,但是 mypy
认为列表可以包含更广泛的抽象类型。
解决方法是更改其中一个注释。可能
self.jobs
应该被暗示为List[FileSystemFileReadable]
。不太可能,__next__
应该返回更宽松的 AbstractFileReadable
,就像协议一样。
另一方面,与您询问的类型提示问题无关,
__iter__
在代码中设置迭代的繁重工作可能是不好的形式。一个更好的主意是在__init__
中进行,而__iter__
只有return self
作为它的整个身体。在当前的实现中,像这样的习语将无法按预期工作:
f = FileSystemFolder("some/path")
first = next(f) # get one value for special processing
rest = list(f) # put the rest of the values in a list
使用您的代码,迭代器生成的第一个元素将与所有其他元素一起出现在
rest
中,因为__iter__
构造函数进行的list
调用会将迭代重置为开始。