目标:用 C 扩展复刻 while item := next(it): 的底层实现,适配 MSYS2 MinGW64 + Python 3.14,避开嵌入模式 WinMain 链接坑。
环境:MINGW64,工作目录
/d/cpp/cpython/Mac
1. 文件清单(Mac 目录下一共 3 个文件)
Mac/
├─ iter_mod.c # C扩展源码,调用CPython迭代API
├─ setup.py # setuptools编译脚本
└─ test_ext.py # Python测试调用脚本
2. 各个文件完整源码
iter_mod.c
#include <Python.h>
static PyObject* iter_demo(PyObject* self, PyObject* args)
{
PyObject *iterable;
if (!PyArg_ParseTuple(args, "O", &iterable))
{
return NULL;
}
PyObject *it = PyObject_GetIter(iterable);
if (!it)
return NULL;
PyObject *item;
int has_data = 0;
while ((item = PyIter_Next(it)) != NULL)
{
has_data = 1;
PyObject_Print(item, stdout, 0);
putchar('\n');
Py_DECREF(item);
}
// PyIter_Next 返回NULL:区分 StopIteration 和其他异常
if (PyErr_Occurred())
{
if (PyErr_ExceptionMatches(PyExc_StopIteration))
{
PyErr_Clear();
}
else
{
Py_DECREF(it);
return NULL;
}
}
Py_DECREF(it);
if (!has_data)
{
printf("Iterable is empty\n");
}
Py_RETURN_NONE;
}
static PyMethodDef IterMethods[] = {
{"iter_demo", iter_demo, METH_VARARGS, "Demo PyIter_Next, same as while item:=next(it)"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef itermodule = {
PyModuleDef_HEAD_INIT,
"iter_mod",
NULL,
-1,
IterMethods
};
PyMODINIT_FUNC PyInit_iter_mod(void)
{
return PyModule_Create(&itermodule);
}
setup.py
from setuptools import setup, Extension
module = Extension("iter_mod", sources=["iter_mod.c"])
setup(
name="iter_mod",
ext_modules=[module]
)
test_ext.py
import iter_mod
lst1 = [1,2,3]
iter_mod.iter_demo(lst1)
print("----")
lst2 = []
iter_mod.iter_demo(lst2)
3. 完整操作步骤(MINGW64 终端,顺序执行)
-
进入工作目录
cd /d/cpp/cpython/Mac
-
安装依赖 setuptools(只需要执行一次)
pacman -S mingw-w64-x86_64-python-setuptools
-
编译 C 扩展模块,自动生成
.pydpython3 setup.py build_ext --inplace
编译成功后,目录出现文件:iter_mod.cp314-mingw_x86_64_msvcrt_gnu.pyd
-
运行测试脚本
python3 test_ext.py
4. 预期输出
1
2
3
----
Iterable is empty
5. 原理对照(《CPython 设计与实现》)
表格
| C API | 等价 Python 语义 |
|---|---|
PyObject_GetIter(obj) |
it = iter(obj) |
PyIter_Next(it) |
item = next(it) |
C 里的循环:
while ((item = PyIter_Next(it)) != NULL)
{
// 处理元素
}
等价 Python 伪代码:
it = iter(seq)
while True:
try:
item = next(it)
except StopIteration:
break
# 处理元素
PyIter_Next返回 NULL 时:
- 如果异常是
StopIteration:代表迭代耗尽,清除异常、退出循环;- 如果是别的异常:直接向上抛出。
6. 补充说明
- 方案选型说明:放弃【C 主程序嵌入 Python 解释器(
Py_Initialize)】。MSYS2 Python 3.14 的导入库内置 GUI 子系统标记,会强制寻找WinMain,链接难以绕过; - C 扩展方案:Python 进程先启动,再加载我们写的 C 代码,属于 CPython 官方推荐的扩展开发方式,无子系统链接冲突,适合底层 API 实验;
- 每次修改
iter_mod.c,都需要重新执行python3 setup.py build_ext --inplace重新编译。
7. 后续可选拓展方向
- 自定义可迭代对象、自定义迭代器,实现
__iter__/__next__协议; - 构造会抛出业务异常的迭代器,观察
PyIter_Next的异常传递; - 配置 VS Code + GDB,断点跟踪进入
PyIter_Next内部源码调试; - 封装接口,完全对齐
while item := next(it):的调用形式。
哈哈没事!咱们把它压到最简,只抓最核心的,不讲复杂名词。
✅ 一句话总结我们刚刚做成的事
写了一段 C 代码,调用 Python 底层原生函数,模拟:
it = iter([1,2,3])
while True:
try:
item = next(it)
print(item)
except StopIteration:
break
也就是书上那句:while item := next(it):
✅ 记住两个最关键的对应关系就够
PyObject_GetIter=iter(对象)PyIter_Next=next(迭代器)
✅ 最简操作清单(以后想用直接复制跑)
cd /d/cpp/cpython/Mac
python3 setup.py build_ext --inplace
python3 test_ext.py
改了 C 代码,就重新跑一遍编译那句。
✅ 剩下的那些:
Py_DECREF、异常清理、setuptools、pyd、ABI、subsystem、WinMain......
暂时不用硬啃,先当成"配套工具胶水",知道它负责不出错就行。
我们慢慢来,想玩哪个小实验,我们就单独拆一小块,少一点文字,一步一步看效果 😊
要不要先就停在这里,或者我们换个更简单的小实验?
一句话大白话
- CMD:Windows 原生自带的老命令壳子(Windows 自家)
- MINGW64(MSYS2) :在 Windows 里套了一个 Bash(Linux 风格终端) ,附带 GCC 编译器、Linux 那一套命令(
ls、gcc、pacman 包管理器),专门用来编译 C/C++
简单对比
表格
| 项目 | CMD(Windows 原生) | MINGW64(MSYS2 里的 bash+mintty) |
|---|---|---|
| 是谁 | Windows 自带,DOS 传承下来 | 跑的是 bash(Linux 常用 shell),窗口程序叫 mintty(就是你截图这个窗口)MSYS2 |
| 路径写法 | D:\cpp\cpython 反斜杠 \ |
/d/cpp/cpython 斜杠 /,和 Linux 一样 |
| 列出文件 | dir |
ls |
| 编译 C 语言 | 默认没有 gcc,要自己折腾 | 自带 gcc、g++,就是我们刚才编译 C 扩展用的 |
| 命令语法 | Windows 一套:copy del |
Linux 风格:cp rm mkdir |
| 包管理 | 没有 | 有 pacman,像 Arch Linux,可以一键装软件(刚才装 setuptools 就是 pacman) |
| 适合干什么 | 简单 Windows 管理、批处理 .bat |
C/C++ 开发、跑 GNU 工具、编译开源代码(我们现在 CPython 底层实验就用它) |
几个很实用的小重点
- MINGW64 编译出来的 exe/pyd,依然是原生 Windows 程序,不是虚拟机,不用额外 dll 就能在普通 Windows 上跑这点很重要。MSYS22
- 两者命令不能直接混用
- cmd 里敲
ls→ 报错 - MINGW64 bash 里敲
dir也不行(除非额外装兼容工具)
- cmd 里敲
- 你截图这个
mintty只是窗口外壳,里面跑的真正解释命令的程是 bash。MSYS22 - 我们刚才做的 C 扩展实验,必须在 MINGW64 里面做,因为要它里面的 gcc、python 环境、编译链;放到普通 cmd 里直接找不到 gcc
CPython 和 C 语言核心技术的关系(深度进阶版)
一句话总结:CPython 本身就是用 C 写的一套庞大程序,它是 C 语言工程的巅峰产物;但 Python 语法本身和 C 完全无关。
拆开讲清楚:
- 核心定义:实现语言 vs 被实现语言
我们从官网下载的标准 Python(CPython),整个解释器、虚拟机、对象模型、内存管理、GIL、内置类型(int/list/dict)、内置函数,全部都是 C 代码写出来的。
你写的 Python 源码(.py)只是一份文本,它本身不是 C;是这个由 C 编译出来的程序(python.exe),读懂了这份文本并执行它。
大神的理解:
C 是实现语言(建造汽车的机器和材料);Python 是被实现的语言(汽车的设计图纸和驾驶规则)。
(你的实战印证):咱们编译出的那个 python.exe,就是这几万行 C 代码结合在一起的终极机器。
- CPython 大量依赖的 C 语言核心技术
这不仅是理论,更是你亲手在 VS Code 里看到的现实:
C 的内存管理(malloc/free 与对象池)
CPython 的对象池、引用计数、分代垃圾回收,底层都是封装 C 的 malloc / free。所有 Python 对象(哪怕是一个数字 1),在底层都是一个 C 结构体 PyObject*。
c
c
// 源码里的基础对象头(你在 typeobject.c 里亲眼见过的)
typedef struct _object {
Py_ssize_t ob_refcnt; // 引用计数(垃圾回收的核心)
struct _typeobject *ob_type; // 指向类型对象的指针
} PyObject;
C 的指针(无处不在的 PyObject*)
整个 CPython 到处是指针,所有对象都靠 PyObject* 操作,这是 C 语言操作内存的核心。
(你的实战印证):你在调试 PyUnicode_InternInPlace 时,左侧变量面板里那个 p = 0x0000003ed1fef0b0,就是纯正的 C 语言指针内存地址。
C 的结构体与宏(宏定义魔法)
CPython 大量使用 C 结构体来构建 Python 的类型系统,并且大量使用宏(Macro)来优化性能。你在阅读源码时看到的那些以 Py_ 或 _Py 开头的,很多都是宏,它们会在编译时被直接替换成 C 代码,以减少函数调用的开销。
C 的函数指针(多态的实现)
Python 为什么能实现面向对象和"鸭子类型"?底层其实是 C 语言的函数指针。每个类型对象(比如 PyUnicode_Type)内部都装满了指向具体 C 函数的指针(如 tp_dealloc, tp_repr)。当你在 Python 里调用 str.upper() 时,虚拟机就是顺着函数指针找到了对应的 C 函数。
C 的编译与链接
CPython 源码要通过 msvc/gcc 编译成二进制可执行文件。我们写的 C 扩展模块(.pyd / .so),也是 C 代码编译后,链接进 CPython 虚拟机,给 Python 调用的。
- 边界:Python 代码 ≠ C 代码
你写的 a = 1; print(a) 这种 Python 代码,不是 C,不会直接编译成机器码;
是 CPython(C 写的虚拟机)把它转成中间产物------字节码(Bytecode),再由 C 语言写的巨型 switch-case 循环(ceval.c 中的 _PyEval_EvalFrameDefault)逐条解释执行。
(你的实战印证):你之前用 dis 模块看到的 LOAD_CONST 和 FORMAT_VALUE,就是字节码。C 虚拟机就是靠读懂这些字节码干活的。
只有 CPython 解释器本身、内置模块、C 扩展,才是 C。
-
串起核心痛点:GIL(全局解释器锁)
GIL 就是 C 代码里的一个互斥锁(Windows 下是 CRITICAL_SECTION,Linux 下是 pthread_mutex),它是操作系统底层的 C 多线程原语封装出来的。
因为 CPython 的内存管理(引用计数 ob_refcnt)在 C 层面不是线程安全的,为了防止多个 C 线程同时修改同一个对象的引用计数导致内存崩盘,CPython 的开发者硬生生加了一把大锁。
这也是为什么 GIL 是 CPython 的痛点:它是 C 层实现的锁,锁住了整个虚拟机的解释执行过程。
-
横向对比:为什么 PyPy 是另一个物种?
CPython:C 写的解释器。提供 Python 语法、虚拟机、运行时。
PyPy:不是 C 写的(主体是用 Python 的子集 RPython 写的),它自带 JIT(即时编译)技术,能把高频执行的 Python 代码直接编译成机器码。所以 PyPy 跑纯 Python 循环极快,但它的 C 扩展兼容性不如 CPython。
🌟 终极补充:如果把视角拔高(更高维的认知)
你之前学的 f-string 对比、深浅拷贝、字符串驻留,其实全都可以在这个框架里找到答案:
关于变量:在 Python 层面,a = "hello" 是把标签贴在对象上;在 C 层面,其实是 C 在内存里分配了一块 PyUnicodeObject 结构体,然后把这块内存的指针赋值给了变量 a。
关于性能:为什么 f-string 比 % 快?因为在 C 语言的底层,Python 3.6+ 对 f-string 的字节码做了极致的特化(FORMAT_VALUE),减少了 C 函数调用的次数。
关于深浅拷贝:在 C 语言层面,浅拷贝只是拷贝了外层的 C 数组指针;深拷贝则是递归地去 malloc 新的内存,把里面的对象也复制了一遍。
小结
✅ 关系:CPython 依托 C 语言的底层能力(指针、内存、编译、系统调用)造出来的 Python 运行时;C 是它的基石。
❌ 误区:Python 不是 C 的语法糖,Python 代码不会自动变成 C 代码。
💡 进阶:搞懂了 C 和 CPython 的关系,你就懂了 Python 为什么会"慢"(解释执行),以及为什么"有些扩展库(如 NumPy)那么快"(底层全是 C 优化)。