You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 17:15:43