如何不使用Union解决mypy子类方法返回类型与父类不兼容报错
解决方案
这里提供三种可实现需求的方案,优先推荐泛型基类方案,既可以保留完整类型检查能力,也不会引入额外运行时依赖。
方案1:泛型公共基类(推荐)
首先你需要调整类结构,抽取完全不绑定同步/异步实现的公共基类,同步、异步客户端分别继承基类并绑定对应的返回类型,从设计层面也更合理:同步和异步客户端调用逻辑完全不同,原本就不满足里氏替换原则,不应该直接让异步类继承同步类。
实现代码
公共基类代码(base.py):
from __future__ import annotations from typing import TypeVar, Generic, TYPE_CHECKING # 仅类型检查阶段导入依赖,运行时不会执行 if TYPE_CHECKING: import requests import aiohttp # 协变类型变量,用于占位返回类型 ResponseT = TypeVar("ResponseT", covariant=True) class BaseClient(Generic[ResponseT]): # 公共方法、公共属性都放在这里实现 def request(self, url: str) -> ResponseT: raise NotImplementedError
同步客户端代码(client.py),完全无异步依赖:
import requests from .base import BaseClient class Client(BaseClient[requests.Response]): def request(self, url: str) -> requests.Response: # 同步请求实现逻辑 return requests.get(url)
异步客户端代码(async_client.py),所有异步依赖仅在这个模块引入,用户不安装异步额外依赖时不会加载该模块就不会报错:
import aiohttp from .base import BaseClient class AsyncClient(BaseClient[aiohttp.ClientResponse]): async def request(self, url: str) -> aiohttp.ClientResponse: # 异步请求实现逻辑 async with aiohttp.ClientSession() as session: resp = await session.get(url) await resp.read() return resp
方案2:类型忽略标记(快速修复)
如果你不想调整现有继承结构,可以直接在重写方法上添加类型忽略标记,让mypy跳过本次返回类型兼容性检查:
class AsyncClient(Client): # type: ignore[override] 仅忽略重写类型不兼容的检查 def request(self, url: str) -> aiohttp.ClientResponse: ...
该方案缺点是会丢失这部分的重写类型校验,仅适合临时快速修复。
方案3:父类返回类型标注为Any
如果不需要对父类request方法做严格的返回类型校验,可以直接把父类返回类型标注为Any,子类返回任意类型都不会触发报错:
from typing import Any class Client: def request(self, url: str) -> Any: ...
该方案缺点是会丢失父类request方法的返回类型检查能力。
内容的提问来源于stack exchange,提问作者tasuren
相关产品推荐
相关产品推荐

