Python借pyi存根打通C扩展模块IDE类型补全

【python】pyi文件是干啥的?函数实现怎么是空的? — 码农高天

码农高天

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)

中文简报

Python借助.pyi存根打通C扩展模块IDE类型补全 C语言扩展模块因缺乏纯Python源码导致编辑器无法解析签名,开发者需通过编写无函数体的.pyi接口存根为IDE提供类型定义与补全桥梁。 视频深入浅出地解释了Python开发者在IDE中跳转源码时常遇到的.pyi空实现文件现象。高天指出后缀中的“i”代表interface,这类存根文件主要用于为C语言等底层扩展模块提供Python语法的函数签名与类型提示,以弥补IDE无法直接解析C源码补全的缺陷。此外,视频还结合作者开源的VizTracer项目实战,讲解了如何为自定义C扩展配置模块名、匹配文件存放路径,以及用省略号(...)编写纯接口定义的具体实践规范。 Python中的.pyi文件后缀代表interface(接口),主要用于为C语言等非Python实现的扩展模块提供函数签名和类型声明。 (0:00) IDE中跳转标准库C扩展模块(如math.sqrt)时看到的.pyi文件并非来自Python标准库本体,而是由Python官方独立维护的Typeshed项目提供。 (0:00) 为自定义C扩展添加.pyi支持时,必须按照纯Python模块的对应路径命名并存放存根文件,函数体只需用省略号(...)或pass占位即可实现完整的IDE代码补全与跳转导航。 (1:21)

Reading summary

码农高天指出,Python IDE在跳转查看部分底层函数时显示为空白实现且后缀为`.pyi`,原因在于这些核心模块由C语言等扩展实现;`.pyi`作为接口存根文件,旨在利用Python语法为静态检查与代码补全提供签名定义。 pyi文件的本质与C扩展提示机制 (0:00) 在VS Code或PyCharm等IDE中,纯Python编写的模块(如`threading.Thread`)可直接跳转至`.py`源码查看实现逻辑,而`math.sqrt`等核心库函数跳转后仅展示无具体实现的`math.pyi`文件。高天解释称,后缀中的字母“i”代表“interface”(接口)。由于`math`等库底层是C语言编写的扩展模块,IDE无法直接解析其C源码来提取暴露的类、函数与参数签名。社区通过引入`.pyi`存根文件,允许使用Python语法声明函数与类的输入输出类型,从而在用户不需了解底层实现的前提下支持补全与导航。此外,IDE中展示的标准库`.pyi`文件并非来自Python标准库本体,而是源于Python官方维护的项目Typeshed。 自定义C扩展中编写存根文件的规范 (1:21) 针对开发者自行构建C扩展模块的场景,高天以其开源性能追踪工具VizTracer为例,说明了为保证IDE补全和类型检查而维护`.pyi`存根的具体规范。首先,在编译配置中定义模块名时,需将其设置为对应主包下的子模块,例如`viztracer.snaptrace`或`viztracer.vcompressor`。其次,存根文件的存放路径遵循纯Python模块的对应检索规则,即`viztracer.snaptrace`扩展的存根文件必须命名为`snaptrace.pyi`并放置在`viztracer`目录下。最后,存根文件内部仅需书写公开的类与函数签名,实现部分直接使用省略号`...`或`pass`填充,开发者可按需补充具体的参数与返回值类型注解以增强推断效果。 掌握`.pyi`存根的编写规则能够帮助编写多语言扩展的开发者解决IDE无法直接解析底层实现的难题,确保跨语言模块在Python开发环境中提供准确的代码跳转与类型提示体验。

Original transcript

[0:00] 我们今天来讲一个很多初学者会遇到的问题大家知道在VSCode或者在PyCharm里我们可以通过Ctrl+左键来查看一个类或者一个函数的实现比如这里我们Ctrl+左键点这个thread我们就会来到Python官方库对这个thread的实现可以看到下面整个就是这个python的Thread的原代码但是对于有的函数比如这个math.square root我们点击之后会发现它是长成这样的他并没有给我们展示Python的这个Square Root是怎么实现的那为什么会有这种情况呢我们注意看右上方这里我们打开的这个文件实际上是math.pyi文件这个I代表的是interface而我们刚刚查看的thread类,它是在threading.py这个python文件里的那这两者的不同本质上是因为math这个酷是用c实现的它并不是用py写的而用C实现的这种扩展它有一个问题就是用户没办法知道你这个扩展提供了什么样的外部接口因此像VSCode PyCharm 这种 IDE 也没法提示这些接口应该怎么用所以 Python 就引入了 PYI 文件它的目的是让你可以用Python的语法去声明这个扩展里面都有哪些函数都有哪些类每个函数和类它的输入是什么输出是什么这样在用户只需要调用它而不需要了解它实现的时候就非常方便当然我们现在看到的这个文件并不是python standard library 的一部分它来源于Python官方维护的另外一个库叫TypeShed这个库是专门给Python的标准库做注解的那我们在开发的过程中如果用到了C扩展有的时候就要自己去写这个注解比如说Vistracer的核心locking部分它是用C来完成的这里我们就可以写一个PYI文件来更好的支持类型检查和IDE提示我们简单说一下这件事怎么完成首先在你build extension这里,它会有一个module名,也就是你写的这个c extension要怎么import进来这里大家一定要把这个名字启成你package下面的一个submodule比如我这个库叫vistracer对吧我这个extension就叫vistracer.snaptrace包括下面这里Vtracer.vcompressor都是Vtracer.什么起好了名字之后你就要写py文件要放到哪他找的方式是这个module如果是用python写的这个文件应该在哪他就在哪我这个module叫vistracer.snap trace对吧如果它是一个python文件它就应该在我Vistracer这个文件夹下的snaptrace.py因此我在这个位置建立一个snaptrace.pyi文件大家看我下面还有一个vcompressor.pyi对应的是我刚才那个vstracer.vcompressor那在这个pyi文件里面你就可以列出你所有的公开的类和函数的接口这些接口的实现你可以写三个点可以写pass类型注释有没有都行和你平时正常的写python文件是一样的有的话就多一点信息没有的话就少一点信息那在有了这个py文件之后你在使用这个c扩展的时候他就提示了我们control+左键点一下就会来到我们写的PY文件如果你平时写一些其他语言的扩展 这是一个比较重要的指使啊哪怕你不写你现在起码知道PY文件是怎么回事对吧好了 这期视频就到这里啊 希望和大家关注 别忘了一件 三连哦!