码农高天
C语言扩展模块因缺乏纯Python源码导致编辑器无法解析签名,开发者需通过编写无函数体的.pyi接口存根为IDE提供类型定义与补全桥梁。
AI-assisted summary of 【python】pyi文件是干啥的?函数实现怎么是空的? — original source. Check the original for full context.
Source published:
Key ideas
- Python 中的 .pyi 文件后缀代表 interface(接口),主要用于为 C 语言等非 Python 实现的扩展模块提供函数签名和类型声明。 (0:00)
- IDE 中跳转标准库 C 扩展模块(如 math.sqrt)时看到的 .pyi 文件并非来自 Python 标准库本体,而是由 Python 官方独立维护的 Typeshed 项目提供。 (0:00)
- 为自定义 C 扩展添加 .pyi 支持时,必须按照纯 Python 模块的对应路径命名并存放存根文件,函数体只需用省略号(...)或 pass 占位即可实现完整的 IDE 代码补全与跳转导航。 (1:21)
Detailed notes
## Background 该视频由“码农高天”于 2025 年 4 月 26 日发布在 YouTube 平台上。视频简介指出,本期内容主要探讨 Python 中 `.pyi` 文件的作用、产生原因以及开发者在什么场景下会使用到它。
## TL;DR 视频深入浅出地解释了 Python 开发者在 IDE 中跳转源码时常遇到的 `.pyi` 空实现文件现象。高天指出后缀中的“i”代表 interface,这类存根文件主要用于为 C 语言等底层扩展模块提供 Python 语法的函数签名与类型提示,以弥补 IDE 无法直接解析 C 源码补全的缺陷。此外,视频还结合作者开源的 VizTracer 项目实战,讲解了如何为自定义 C 扩展配置模块名、匹配文件存放路径,以及用省略号(`...`)编写纯接口定义的具体实践规范。
## Atmosphere & style 视频风格务实明快、干货满满,呈现出典型的资深工程师答疑解惑节奏。讲解由日常编码中直观的跳转现象切入,迅速击中开发者的常见困惑,随后的实战演示结合个人开源项目,语气沉稳自信且极具说服力。整体观感短小精炼、直奔主题,让观众在几分钟内轻松解开底层机制的迷雾。
## Bulletin - [00:00](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=0s) Python 中的 .pyi 文件后缀代表 interface(接口),主要用于为 C 语言等非 Python 实现的扩展模块提供函数签名和类型声明。 - [00:00](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=0s) IDE 中跳转标准库 C 扩展模块(如 math.sqrt)时看到的 .pyi 文件并非来自 Python 标准库本体,而是由 Python 官方独立维护的 Typeshed 项目提供。 - [01:21](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=81s) 为自定义 C 扩展添加 .pyi 支持时,必须按照纯 Python 模块的对应路径命名并存放存根文件,函数体只需用省略号(...)或 pass 占位即可实现完整的 IDE 代码补全与跳转导航。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=0s) 在 IDE 中点击标准库纯 Python 实现(如 threading.Thread)可直接查看完整实现代码,而点击底层由 C 编写的模块(如 math.sqrt)则会跳转到空实现的 .pyi 文件。 - [00:00](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=0s) .pyi 文件中的字母“i”代表 interface(接口),其本质是类型存根文件(Stub File)。 - [00:00](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=0s) 由于 IDE 无法直接解析 C 扩展编译出的二进制文件来提取符号和参数签名,因此需要通过 Python 语法的 .pyi 声明对外暴露的模块、类与函数接口。 - [00:00](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=0s) IDE 中常见的标准库 .pyi 接口定义由 Python 官方的 Typeshed 项目集中维护,并不属于 Python 标准库本体代码。 - [01:21](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=81s) 编写自定义 C 扩展模块时,若需支持代码提示和静态检查,开发者同样需要手动配置并维护配套的 .pyi 文件(如 VizTracer 性能追踪库)。 - [01:21](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=81s) C 扩展构建配置中的模块名称应规范命名为对应主包的子模块(例如 viztracer.snaptrace)。 - [01:21](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=81s) IDE 检索存根文件的逻辑与纯 Python 模块路径一致,例如 viztracer.snaptrace 模块对应的存根必须命名为 snaptrace.pyi 并存放在 viztracer 目录下。 - [01:21](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=81s) .pyi 文件内部无需具体执行逻辑,函数体直接使用省略号(...)或 pass 占位即可生效。 - [01:21](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=81s) 在 .pyi 文件中补充类型注解可以提供更强的类型推断,即使不标注类型也能保证 IDE 正确识别函数名和形参结构。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=0s) 揭秘 `.pyi` 文件:为什么跳转查看源码时实现是空的?
在日常使用 VS Code 或 PyCharm 等主流 IDE 编写 Python 代码时,许多开发者都习惯通过“Ctrl + 鼠标左键”点击某个类或函数来跳转查看底层实现。比如点击标准库中的 `Thread` 类,IDE 会直接打开 `threading.py`,完整展示该类用纯 Python 编写的源代码。然而,当开发者尝试点击 `math.sqrt` 等函数时,却会发现弹出的内容几乎是“空的”,并未包含具体的算法实现,而且文件后缀变成了 `.pyi`(即 `math.pyi`)。
码农高天解释道,文件名中的字母“i”实际上代表的是“interface”(接口)。之所以出现只有接口定义而没有具体实现的情况,本质原因在于 `math` 等核心库底层是用 C 语言编写的 C 扩展模块,而非纯 Python 代码。对于 C 扩展而言,IDE 无法直接通过解析 Python 源码来获取其对外暴露了哪些类、函数以及参数签名,从而无法向用户提供代码自动补全和类型提示。为了解决这一痛点,Python 社区引入了 `.pyi` 文件,其核心作用在于允许开发者使用 Python 本身的语法来声明 C 扩展中所包含的模块、函数与类,并明确注明输入参数和返回值类型。通过这种方式,用户在仅需调用接口而无需关心底层 C 实现细节时,就能获得流畅的开发体验。高天还补充说明,我们在 IDE 中看到的标准库 `.pyi` 文件其实并不属于 Python 标准库本体,而是来自于 Python 官方维护的专门项目 Typeshed。
正如码农高天所总结的:“它的目的是让你可以用Python的语法去声明这个扩展里面都有哪些函数都有哪些类,每个函数和类它的输入是什么输出是什么,这样在用户只需要调用它而不需要了解它实现的时候就非常方便。”
```mermaid flowchart TD accTitle: C扩展与pyi接口提示工作原理 accDescr: 展示IDE如何通过pyi存根文件为C语言扩展提供Python语法级别的接口提示与类型检查。
subgraph C_Extension ["底层底层实现 (C Extension)"] CExt["C 源码编译生成的动态库 (.so /.pyd)"] end
subgraph Interface ["类型存根 (Typeshed /.pyi)"] PyiFile["math.pyi (函数签名、参数类型、返回值,函数体为... 或 pass)"] end
subgraph Developer_IDE ["开发者开发环境 (IDE / 类型检查器)"] IDE["VSCode / PyCharm / Mypy"] UserCode["用户代码 (math.sqrt)"] end
UserCode --> IDE IDE -.->|"读取签名与类型定义"| PyiFile IDE -->|"运行时实际调用"| CExt ```
- **普通 Python 模块**:以 `threading.Thread` 为例,定义在 `threading.py` 中,跳转后可直接阅读完整的纯 Python 业务逻辑与实现。 - **C 语言扩展模块**:以 `math.sqrt` 为例,底层由 C 编写,IDE 跳转时定位到 `math.pyi` 存根文件(Stub File)。 - **`.pyi` 命名含义**:后缀中的“i”代表 interface(接口)。 - **Typeshed 仓库**:Python 官方维护的类型注解库,专门为 Python 标准库及热门第三方库提供 `.pyi` 存根支持。
---
[01:21](https://www.youtube.com/watch?v=cuFpTXaZP8o&t=81s) 如何为自己的 C 扩展编写 `.pyi` 接口存根文件
在实际项目开发中,如果开发者编写了自己的 C 扩展模块,为了让项目获得良好的 IDE 补全提示和静态类型检查,同样需要手动编写 `.pyi` 文件。高天以自己开源的性能追踪工具 VizTracer 为例进行了演示:VizTracer 的核心 logging 与 tracing 逻辑为了保证高性能是用 C 语言实现的,为了配合现代编辑器的类型检查与补全功能,项目内就额外维护了对应的 `.pyi` 文件。
实现这一规范主要包含以下关键步骤与规则: 1. **模块命名规范**:在编译扩展(build extension)配置中设定 module 名称时,必须将其命名为对应主 package 下的子模块(submodule)。例如主库名称为 `viztracer`,那么 C 扩展的模块名就应命名为 `viztracer.snaptrace` 或 `viztracer.vcompressor`。 2. **文件存放路径与命名匹配**:IDE 寻找 `.pyi` 文件的检索逻辑遵循“若该模块是由纯 Python 编写,它应该位于哪个文件路径,`.pyi` 就必须放在该路径下”。因此,对于名为 `viztracer.snaptrace` 的扩展,对应的存根文件应当放置在 `viztracer` 文件夹下,并命名为 `snaptrace.pyi`;同理,`viztracer.vcompressor` 对应为 `vcompressor.pyi`。 3. **接口文件内容编写**:在 `.pyi` 文件中,开发者只需列出所有对外公开的类、方法与函数声明。函数体内部不需要具体的执行逻辑,直接填入三个点省略号(`...`)或者 `pass` 即可。类型注解可根据实际需求选择添加或省略,添加类型注解能够提供更丰富的类型推断,不加类型注解也能至少保证函数名与入参结构得到 IDE 正确识别。
码农高天强调掌握这一机制的价值:“如果你平时写一些其他语言的扩展,这是一个比较重要的知识啊,哪怕你不写,你现在起码知道PYI文件是怎么回事,对吧?”完成 `.pyi` 文件的配置后,用户在调用 C 扩展接口并执行 Ctrl + 左键 跳转时,IDE 就能准确导向该接口文件,带来丝滑的代码补全与导航体验。
## Mentioned 码农高天, Python, VS Code, PyCharm, Typeshed, VizTracer, Mypy, C语言
## Video description 这期视频来聊一下pyi文件,它是干什么的,为什么会出现,以及我们什么时候会用到它。
[Source](https://www.youtube.com/watch?v=cuFpTXaZP8o)