Python类型提示:让mypy自动推断依赖其他方法的返回值类型
实现方法
要让mypy自动根据子类files属性的类型推导paths的返回值类型,核心是给基类增加泛型参数,建立files类型和paths返回类型的绑定关系,不需要在每个子类重复写paths的类型注解。
兼容Python 3.10+、mypy 1.0+的通用写法
需要先从typing模块导入泛型相关工具,修正原代码的语法错误(子类定义后缺冒号),完整可通过检查的代码如下:
from abc import ABC, abstractmethod from pathlib import Path from typing import TypeVar, Generic, cast # 定义泛型类型变量,绑定files属性支持的三种输入类型 T = TypeVar("T", str, list[str], dict[str, str]) class Foo(Generic[T], ABC): @property @abstractmethod def base_path(self) -> Path: ... @property @abstractmethod def files(self) -> T: ... @property def paths(self) -> Path | list[Path] | dict[str, Path]: # 类型窄化逻辑mypy可自动识别 if isinstance(self.files, str): return cast(T, self.base_path / self.files) if isinstance(self.files, list): return cast(T, [self.base_path / file for file in self.files]) if isinstance(self.files, dict): return cast(T, {key: self.base_path / file for key, file in self.files.items()}) raise TypeError("Unsupported files type") # 子类继承时传入对应files的类型作为泛型参数 class Bar(Foo[str]): base_path = Path("xyz") files = "file1" class Baz(Foo[list[str]]): base_path = Path("xyz") files = ["file1", "file2"] class Qux(Foo[dict[str, str]]): base_path = Path("xyz") files = {"config": "config.json", "log": "run.log"}
cast是类型检查器专用标记,不会产生运行时开销,也不会修改实际返回值,仅用于帮mypy关联泛型类型和分支返回值的对应关系。
验证推导效果
用mypy的reveal_type可以验证推导结果:
bar = Bar() reveal_type(bar.paths) # mypy输出: Revealed type is "pathlib.Path" baz = Baz() reveal_type(baz.paths) # mypy输出: Revealed type is "list[pathlib.Path]" qux = Qux() reveal_type(qux.paths) # mypy输出: Revealed type is "dict[str, pathlib.Path]"
Python 3.12+ 更简洁的写法
Python 3.12 支持PEP 695的新泛型语法和类型别名语法,可以直接定义类型映射关系,不需要额外导入TypeVar、Generic:
from abc import ABC, abstractmethod from pathlib import Path # 定义files类型到paths返回类型的映射 type PathsFor[T] = ( Path if T is str else list[Path] if T is list[str] else dict[str, Path] ) class Foo[T](ABC): @property @abstractmethod def base_path(self) -> Path: ... @property @abstractmethod def files(self) -> T: ... @property def paths(self) -> PathsFor[T]: if isinstance(self.files, str): return self.base_path / self.files if isinstance(self.files, list): return [self.base_path / file for file in self.files] if isinstance(self.files, dict): return {key: self.base_path / file for key, file in self.files.items()} raise TypeError("Unsupported files type") # 子类不需要显式传泛型参数,mypy可从files属性自动推导 class Bar(Foo): base_path = Path("xyz") files = "file1" class Baz(Foo): base_path = Path("xyz") files = ["file1", "file2"]
原写法不生效的原因
原来的基类中paths返回类型被写死为Path | list[Path] | dict[str, Path]的联合类型,mypy没有拿到files类型和paths返回类型的关联关系,无法根据子类的files类型自动窄化返回值,只能识别为联合类型。
内容的提问来源于stack exchange,提问作者Hyperplane
相关产品推荐
相关产品推荐

