Protocol与类型注解实现是否正确?为何mypy检测报错?
代码定义与问题描述
定义的Protocol类
@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")
用户疑问
这样实现和使用Protocol与类型注解是否正确?
问题原因与修正方案
问题根源
FileSystemFolder的__next__方法声明返回FileSystemFileReadable,但self.jobs被标注为List[AbstractFileReadable]。mypy会严格按照静态类型检查:从AbstractFileReadable类型的列表中取出的元素,只能确定是抽象父类类型,无法直接推导为其子类FileSystemFileReadable,因此触发类型不兼容错误。
你的Protocol核心实现逻辑没问题,但在类型标注细节上需要调整。
修正步骤
修正
self.jobs的类型标注
既然初始化时明确创建的是FileSystemFileReadable实例,直接把self.jobs的类型改为List[FileSystemFileReadable]即可消除类型不匹配:def __iter__(self) -> "FileSystemFolder": self.jobs: List[FileSystemFileReadable] = [ FileSystemFileReadable(path) for path in [*glob(os.path.join(self.path, "*"))] ] self.current: int = -1 return self优化Protocol的迭代器注解
按照Python迭代器规范,__iter__方法更合理的返回类型是Iterator[AbstractFileReadable],这样实现类返回自身(FileSystemFolder是Iterator[FileSystemFileReadable],属于Iterator[AbstractFileReadable]的协变类型)时,mypy会认可这种兼容关系:from typing import Iterator, Protocol, runtime_checkable @runtime_checkable class AbstractFolder(Protocol): def __iter__(self) -> Iterator[AbstractFileReadable]: raise NotImplementedError def __next__(self) -> AbstractFileReadable: raise NotImplementedError可选:用协变类型变量增强灵活性
如果希望Protocol支持更灵活的子类返回类型,可以引入协变类型变量,让抽象协议和具体实现的类型绑定更紧密:from typing import Iterator, Protocol, runtime_checkable, TypeVar T = TypeVar("T", bound=AbstractFileReadable, covariant=True) @runtime_checkable class AbstractFolder(Protocol[T]): def __iter__(self) -> Iterator[T]: raise NotImplementedError def __next__(self) -> T: raise NotImplementedError class FileSystemFolder(AbstractFolder[FileSystemFileReadable], Iterator): # 其余代码保持不变 def __next__(self) -> FileSystemFileReadable: # 代码不变
总结
你的Protocol和类型注解的核心思路是正确的,问题仅出在类型标注的细节不匹配上。按照上述步骤修正后,mypy的类型检查就能通过。
内容的提问来源于stack exchange,提问作者Tlaloc-ES

