如果你一直在关注 Python 的类型检查生态,最近应该注意到一个不大但很有意思的消息:Basilisk 已经从 Python typing conformance leaderboard 中被移除了。
这个事件,单看似乎只是某个小众工具的下架,但在 Python typing 生态正在快速走向“规范收敛”的当下,它传递的信号远比新闻本身重要。
这篇文章不想只复述一条动态。我会先解释 Basilisk 是什么、conformance leaderboard 在测什么、为什么会有工具被移除,再进一步分析:作为普通 Python 开发者,这件事对你选择类型检查工具、编写类型注解、排查 ImportError 错误,到底有没有实际影响。最后会给出可落地的自查方法和工程建议。
无论你是刚接触 Python typing 的新手,还是在团队里推广类型检查的工程负责人,这篇文章都值得读完并收藏备用。
1. 这件事真正要解决的问题
先说结论:Basilisk 被移除,不等于 Basilisk 项目死亡,也不代表它“一无是处”。更合理的理解是——Python typing 生态正在从“百花齐放”走向“标准收敛”,而 conformance leaderboard 就是这个收敛过程的裁判席。
为什么这件事值得关注?因为对于绝大多数 Python 开发者来说,类型检查工具的选择已经从一个“可选项”变成了“工程必选项”。尤其在 PyCharm、VSCode Pylance、mypy、Pyright 等工具已经深度融入日常开发的今天,了解这些工具背后的评测机制,能帮你回答几个非常现实的问题:
- 我的代码用哪个类型检查器最稳?
- 为什么同一个注解,在这台机器上没问题,在另一台机器上就报
ImportError? - 当工具说“支持 typing 规范”时,它到底支持到什么程度?
- 如果某个工具退出评测,我项目里的代码会不会受影响?
这篇文章要解决的就是这些问题。我会从事件本身出发,把 Basilisk 的设计理念、conformance leaderboard 的评测机制、以及开发者如何应对“工具变动”这三件事讲清楚。
2. Basilisk 是什么:一个不走寻常路的类型检查器
要理解 Basilisk 被移除的意义,得先知道 Basilisk 是什么。
Basilisk 是一个 Python 类型检查器,但它的设计理念和 mypy、Pyright 有本质区别。主流类型检查器大多基于静态分析:直接扫描源码、解析 AST(抽象语法树)、按照类型规则推断每个变量和函数的类型。静态分析的优点是快、不执行代码,缺点是遇到动态特性(比如eval、动态属性、运行时生成的类)时容易出现漏判或误判。
Basilisk 的思路反向而来:它不直接静态分析你的代码,而是先运行代码,捕获运行时暴露出的 Python API 接口信息,再把这些信息转换成类型检查的依据。
用一句话概括:别人的类型检查是“读源码猜类型”,Basilisk 是“跑一遍看类型”。
这种设计有几个明显优势:
- 对动态特性的容忍度高。只要代码能在运行时跑通,Basilisk 就能捕获到真实的接口签名。
- 学习成本低。它不需要开发者额外维护复杂的类型标注,甚至可以从无注解代码开始逐步引入类型。
- 和 Python 运行时结合紧密。它依赖的不是纯文本层面的推断,而是运行时对象信息。
但“运行代码再检查”也有天然短板:它要求代码能真正执行起来。如果项目的某段代码依赖外部服务、数据库、GUI 环境,或者导入时就报错,Basilisk 就很难完整覆盖。这也是它在部分评测场景中表现受限的原因之一。
| 特性 | Basilisk | mypy | Pyright |
|---|---|---|---|
| 检查方式 | 运行时 API 探索 | 静态分析 AST | 静态分析 + 类型推断 |
| 对动态特性支持 | 较好 | 一般 | 一般 |
| 是否需要执行代码 | 是 | 否 | 否 |
| 上手成本 | 较低 | 中等 | 低 |
| 适合场景 | 遗留动态代码逐步引入类型 | 严格类型约束项目 | 大型项目 + IDE 集成 |
从材料看,Basilisk 被移出 conformance leaderboard 的公告并没有给出复杂的解释,但更稳妥的判断是:这个决定和 Basilisk 的“非主流”设计有关——leaderboard 更看重对 typing 规范本身的符合度,而 Basilisk 的目标更多是运行时的实用覆盖,两者在评测中出现了目标错位。
3. Python typing conformance leaderboard 到底在测什么
先说一个最容易误会的点:conformance leaderboard 不是“工具性能排行榜”,也不是“哪个检查器运行更快”的比赛。它评测的核心是:各个类型检查器对 Python typing 规范(PEP 系列)的符合程度。
Python 的类型注解体系不是一蹴而就的。从 PEP 484(类型注解)、PEP 526(变量注解)、PEP 544(协议)、PEP 586(字面量类型)、PEP 589(TypedDict)、PEP 591(Final)、PEP 604(联合类型写法)、PEP 655(Required/NotRequired)到 PEP 695(泛型新语法),Python 逐年扩展着类型系统的能力边界。
这些 PEP 定义的是“标准”。但标准写出来是一回事,检查器能不能按标准实现是另一回事。conformance leaderboard 就充当了这个“监考老师”:它准备了一批覆盖各类 typing 特性的测试用例,然后逐个检查每个类型检查器能否正确识别、推断和校验这些用例。
评测一般会覆盖这些维度:
- 基础类型:
int、str、List[int]、Dict[str, int]等。 - 泛型:
Generic[T]、TypeVar的约束与协变逆变。 - 协议:
Protocol与结构化子类型。 - TypedDict:字典键的类型约束,以及
Required/NotRequired等修饰。 - 重载:
@overload的匹配逻辑。 - 类型窄化:
isinstance、TypeGuard、cast的使用。 - 字面量类型与 Final。
- 新语法兼容性:如
X | Y和type语句。
如果你的代码天天都在用List[int]和Optional[str],你可能觉得这些测试“很简单”。但真正实现一个类型检查器,让它在各种组合场景下都给出符合规范的结论,难度非常高。这也是为什么市面上成熟的检查器屈指可数。
用一张表概括当前常用的类型检查器与规范符合度特点:
| 检查器 | 主要维护方 | 规范符合度表现 | 集成方式 |
|---|---|---|---|
| mypy | 社区驱动 | 传统、覆盖广、更新稳健 | 命令行、pre-commit、mypy daemon |
| Pyright | Microsoft | 覆盖高、速度较快 | Pylance(VSCode)、命令行、node 环境 |
| pytype | 覆盖动态特性较好 | 命令行、与 Bazel 集成 | |
| pyre | Meta | 性能强、适合大型代码库 | 命令行、watchman 集成 |
| Basilisk | 独立开发者 | 偏离规范细节较多、退出评测 | 独立运行 |
从这张表可以看出,Basilisk 更接近“探索型”工具:它的价值未必在所有场景都能发挥,但它的设计思路为社区提供了不同方向的参考。
4. Basilisk 被移除这件事,意味着什么
回到事件本身。Basilisk 从 leaderboard 被移除,我认为至少可以从三个层面解读。
4.1 对 Basilisk 项目本身的直接信号
leaderboard 的移除,本质上是一个“资格声明”:在既定评测标准下,Basilisk 已经没有继续参与排名的必要,或者它主动选择了退出。这并不等同于项目的技术失败。更合理的理解是:Basilisk 的项目目标和 leaderboard 的目标不一致。
leaderboard 想要的,是尽可能严格地遵循 PEP 规范;Basilisk 想要的,是用运行时 API 信息来辅助现实项目落地类型检查。当两者目标不一致时,继续保留在榜单上反而会产生误导——读者可能以为它在“规范符合度”上和其他工具同台竞技,但其实它走的是另一条路。
4.2 对 Python typing 生态的行业信号
Basilisk 被移除,更值得留意的信号是:Python typing 生态正在从“工具功能竞争”过渡到“规范符合度竞争”。
几年前,各类型检查器的差异主要在于“你能检查多少种特性”。现在,随着 PEP 规范体系逐步稳定,工具的竞争点开始转向“谁能更准确地实现规范”。这种变化的结果是:对普通开发者而言,不同检查器之间的“方言差异”会越来越小,代码迁移成本也会随之降低。
反过来看,这也意味着那些“规范符合度不够”的工具,如果不调整方向,就会逐渐被主流生态边缘化。Basilisk 不是第一个,也不会是最后一个。
4.3 对开发者的实际影响
从短期看,Basilisk 被移除不会影响你的日常开发。mypy 和 Pyright 依然是主流选择,Pylance 依然在 VSCode 里为你提供智能提示。
但从长期看,这件事提醒我们:不要把项目“焊死”在某一个类型检查工具上。工具会变,排行榜会变,但代码质量的标准不会变。你的代码应该尽量遵循 typing 规范本身,而不是迎合某个检查器的偏好。
5. 类型检查工具格局:从功能竞争到规范收敛
理解趋势之后,我们再回头看看当前 Python 类型检查的格局。为什么 mypy、Pyright 能成为主流,而 Basilisk 这样的探索型工具只能待在角落?
5.1 mypy:传统但可靠
mypy 是最早被广泛接受的 Python 类型检查器,诞生于 2012 年左右。它的优势是“老”:文档多、踩坑经验多、和 Django、SQLAlchemy 等框架的适配方案都齐全。劣势是速度偏慢,特别是在大型项目上。
5.2 Pyright / Pylance:IDE 时代的最佳拍档
Pyright 由微软维护,采用 TypeScript 编写,速度远超 mypy。它内置在 VSCode 的 Pylance 扩展中,开箱即用,几乎零配置。对于新项目,我通常会推荐从 Pyright 入手。
5.3 pytype / pyre:大厂内部工具的开源版本
Google 的 pytype 和 Meta 的 pyre 都偏向大型代码库,强调推断能力强、支持增量检查。它们的设计目标更多是“内部工程效率”,对普通中小项目的体验不一定比 Pyright 更好。
5.4 规范收敛对选型的影响
当所有主流工具都在向同一套 PEP 规范靠拢时,选型逻辑会变得更简单:
- 团队成员都在用 VSCode,优先 Pyright/Pylance。
- 团队有严格的 CI 流程和 pre-commit 习惯,mypy 更稳妥。
- 代码库非常庞大、动态特性多,可以考虑 pytype。
- 完全依赖运行时信息、想低成本引入类型的探索型项目,Basilisk 的思路仍有参考价值。
真正稳定的判断是:工具可以换,但你对 typing 规范的理解必须扎实。只要代码符合规范,任何检查器都能用;如果代码只针对某个检查器的“偏好”去写,那换工具就有风险。
6. 对普通开发者的实际影响:一个真实的 ImportError 案例
接下来这部分,我们落到代码层面。
记得热搜词里有这样一条错误信息:ImportError: cannot import name 'NotRequired' from 'typing'。这个错误在 Python 版本升级时非常典型,也和 typing 规范收敛直接相关。
NotRequired是 PEP 655 引入的 TypedDict 修饰符。它表示某个字典键“不是必需的”,即实例化 TypedDict 时可以不传这个键。但问题在于:只有 Python 3.11 及以上的版本,NotRequired才被正式收录进标准库typing。
如果你的开发环境是 Python 3.10 或更早版本,直接写:
from typing import NotRequired就会得到开头的ImportError。
正确做法有两种:
# 方案一:使用 typing_extensions,兼容旧版本 from typing_extensions import NotRequired, TypedDict# 方案二:仅限 Python 3.11+ from typing import NotRequired, TypedDict为什么会发生这种错误?因为 typing 生态的规范是逐年演进的,标准库的更新速度快于很多人的 Python 版本升级速度。你的工具链都在“规范收敛”的路上,但你的运行环境可能还没跟上。
这个例子很好地说明了:使用 typing 特性前,先确认 Python 版本支持范围,而不是默认某个注解写法在所有环境都能运行。用错版本导致 ImportError,往往会被误判为“代码问题”或“环境坏了”,实际只是版本不一致。
7. 自查代码兼容性:从最小示例开始
7.1 先做一个最小项目
假设我们要检查一段代码在不同类型检查器下的表现。创建以下目录结构:
typing_compat_check/ ├── app.py ├── pyproject.toml └── requirements-dev.txtapp.py的内容如下:
# 文件路径:typing_compat_check/app.py from typing import TypedDict class UserProfile(TypedDict): name: str age: int email: str | None def get_user_display_name(user: UserProfile) -> str: return user["name"].title()这段代码用到了两个特性:
TypedDict:从 PEP 589 开始支持。str | None:PEP 604 联合类型写法,Python 3.10 之后可用。
7.2 安装多个检查器进行对比
创建requirements-dev.txt:
mypy>=1.0 pyright>=1.1.300 typing_extensions>=4.8.0安装依赖:
pip install -r requirements-dev.txt如果安装较慢,可以分别安装单独的工具,例如:
pip install mypy pyright typing_extensions7.3 运行 mypy
mypy app.py预期输出(如果一切正常):
Success: no issues found in 1 source file7.4 运行 Pyright
Pyright 是通过 npm 分发的,但pip install pyright也能安装一个较新的二进制版本。运行方式:
pyright app.py预期输出:
0 errors, 0 warnings, 1 file7.5 故意引入兼容性错误,观察报错差异
把app.py改成一个“旧版本不兼容”的版本:
# 文件路径:typing_compat_check/app_bad.py from typing import NotRequired, TypedDict class UserProfile(TypedDict): name: str age: int email: NotRequired[str] def get_user_age(user: UserProfile) -> int: return user["age"] * 2在 Python 3.10 或更低版本中,运行:
python app_bad.py会得到:
ImportError: cannot import name 'NotRequired' from 'typing'而运行 mypy:
mypy app_bad.py如果 mypy 版本较新,并且你设置了python_version = "3.10",它可能会正常通过,因为 mypy 自己实现了NotRequired的语义检查,不依赖运行时环境中的typing模块。这恰恰体现了“类型检查器的规范实现”和“运行时的 typing 模块版本”不是一回事。
正确的修复方式,是把导入改为typing_extensions:
# 文件路径:typing_compat_check/app_fixed.py from typing_extensions import NotRequired, TypedDict class UserProfile(TypedDict): name: str age: int email: NotRequired[str] def get_user_age(user: UserProfile) -> int: return user["age"] * 2再次运行:
python app_fixed.py你会看到程序正常结束,没有任何错误。
7.6 通过 pyproject.toml 固定检查器配置
推荐在项目根目录配置pyproject.toml,明确工具的运行目标和 Python 版本:
[tool.mypy] python_version = "3.10" strict = true warn_return_any = true warn_unused_configs = true [tool.pyright] pythonVersion = "3.10" typeCheckingMode = "strict" reportMissingTypeStubs = false这段配置的作用是:无论你本地 Python 是什么版本,类型检查器都会按 3.10 标准来评估代码,避免“本机能过、CI 过不了”的尴尬。
8. 常见问题与排查思路
在实际使用 typing 和类型检查器的过程中,很多报错其实和工具本身无关,而是版本、配置或导入方式的问题。下面整理几张排查清单。
8.1 ImportError 类问题
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
cannot import name 'NotRequired' from 'typing' | Python 版本低于 3.11 | 执行python --version | 改用typing_extensions导入,或升级 Python |
cannot import name 'TypeGuard' from 'typing' | Python 版本低于 3.10 | 检查 Python 版本 | 改用typing_extensions,或升级 |
安装了typing_extensions但依然报错 | 环境中存在多个 Python 环境 | 执行which python和pip show typing_extensions | 确认 pip 和 python 属于同一虚拟环境 |
| 本地不报错,CI 报错 | CI 镜像 Python 版本和本地不一致 | 查看 CI 日志中的python --version | 统一项目python_version配置,并用 Docker/venv 锁定 |
8.2 类型检查器行为差异
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| mypy 通过,Pyright 报错 | 两个工具对某些特性的实现细节不同 | 查看具体错误行和规则名 | 优先遵循 PEP 规范写法,而不是迁就某一工具 |
| Pyright 通过,mypy 报错 | mypy 对动态类型更保守 | 运行mypy --strict看详细报告 | 在pyproject.toml中按需放宽规则 |
| 类型检查通过,运行时却报错 | 注解只用于检查,不影响运行时 | 检查真实的数据结构 | 修正数据构造逻辑,或使用cast明确类型 |
| 检查器大量标红第三方库 | 缺少第三方库类型 stub | 查看是否安装了对应类型包 | 安装types-requests、types-PyYAML等 stub 包 |
8.3 工具安装类问题
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
pyright命令找不到 | pip 安装版本未加入 PATH | 执行python -m pyright | 使用python -m pyright代替 |
| mypy 安装后运行失败 | 版本过旧或 Python 版本过新 | 查看错误堆栈 | 升级pip install -U mypy |
| 项目里多个虚拟环境混乱 | 没有使用虚拟环境工具 | 查看当前环境路径 | 统一使用venv或uv管理环境 |
9. 工程建议:如何与 typing 生态相处
Basilisk 被移出 leaderboard,给我们的最大提醒是:工具不是永恒的,规范才是锚点。下面的工程建议可以直接用在你的项目里。
9.1 锁定 Python 和 typing_extensions 版本
如果你的项目需要兼容 Python 3.10 及以下版本,建议在requirements.txt或pyproject.toml中显式引入:
typing_extensions>=4.8.0这样,NotRequired、TypeGuard、TypeVarTuple等新特性都可以通过typing_extensions安全导入。
9.2 编写“最小公倍数”风格注解
尽量使用所有主流检查器都能识别的规范写法,而不是某个工具的私有扩展。例如:
- 用
list[int]而不是List[int](前提是 Python 3.9+)。 - 用
X | Y而不是Optional[X](前提是 Python 3.10+)。 - 用
TypedDict时,优先从typing_extensions导入,以保持低版本兼容。
9.3 不要把检查器配置藏在 IDE 里
团队协作时,把 mypy/Pyright 的配置写进pyproject.toml,而不是让每个人在 IDE 里手动配置。这样才能保证 CI、本地开发、同事电脑上的检查口径一致。
9.4 关注 PEP 进度,但不盲目追新
Python 类型系统的演进很规律。每次新 PEP 发布,typing_extensions会先提供兼容实现,后续版本再进入标准库。建议开发团队每半年检查一次 Python 版本和 typing 特性支持情况,在版本约束允许的范围内逐步采用新写法。
9.5 用 CI 强制类型检查
把类型检查放入 CI 的最简单方式:
- name: Run type checks run: | pip install mypy pyright mypy app pyright app这样工具是否还留在某个排行榜上,其实并不重要;重要的是你的项目已经建立了不依赖具体工具的“规范检查”流程。
10. 总结与后续学习方向
Basilisk 被移出 Python typing conformance leaderboard,不是一次简单的下架操作。它反映了 Python typing 生态正在从“功能竞争”进入“规范收敛”阶段:工具本身会更新换代,但 PEP 定义的类型规范才是各类工具共同遵循的基础。
对开发者来说,最实际的三点收获是:
- 了解 leaderboard 的评测逻辑,不要轻信“工具 A 比工具 B 好”的结论,要看各自的适用场景。
- 正确使用
typing_extensions处理跨版本兼容,特别是NotRequired、TypeGuard这类新特性。 - 在工程实践中,把代码规范建立在对 typing 规范的理解上,而不是绑定在某个具体工具上。
如果你对这个方向感兴趣,下一步可以从这几个点继续深入:
- 阅读 PEP 484、PEP 589、PEP 655、PEP 695 原文,建立对 typing 规范的系统认知。
- 在你的项目里同时接入 mypy 和 Pyright,观察两者的差异,理解“规范符合度”的实际含义。
- 研究
typing_extensions源码,了解它是如何在旧版 Python 上模拟新特性的。
最后提醒一句:工具可以退出排行榜,但你的代码规范不必跟着某个工具走。理解规范、遵守规范,才是长期最优解。建议收藏这篇文章,下次再遇到ImportError: cannot import name 'NotRequired' from 'typing'时,直接按第 7 节的自查流程处理即可。