“我用 Python 写的那段代码,最后跑起来竟然是一个纯正的 C 可执行文件。”

他盯着屏幕上的 build 目录,像是在看一扇刚刚打开的门。
门后不是更快的 Python。
而是另一条路。
一个让 Python 去生成 C 的路。
一个叫 PythoC 的新项目,尝试把我们习惯的“运行时魔法”,改造成“编译时的秩序”。
更意外的是,它把大家以为的缺点,硬生生变成了优点。
每次运行都要即时编译?
不,它想让你每次都能“造出”一份独立的 C 程序。
这不是加速。
这是改写规则。
不是加速,而是改写游戏规则
Python 和 C 的关系,远比我们想象的更亲密。
Python 的“心脏”就是用 C 写的。
许多第三方库也把 C 包在了 Python 外面。
我们早就知道有 Cython 这种办法,用类型注释给 Python 生成 C 扩展模块。
但 PythoC 的路子有点野。
它用类型提示的 Python,程序化地生成 C 代码。
而且它主打独立用途。
不是把 C 模块塞回 Python。
而是让 Python 变成一个 C 代码生成器。
你在函数上贴一个 @compile 装饰器。
你给返回值和每个参数都加上类型提示。
不是 int。
是它自带的 i32int 这样“机器原生”的整数类型。
你会看见一次不小的停顿。
由于它每次执行,都会即时编译 C 代码。
这听起来像一个“致命缺陷”。
但它的反转也从这开始。
你换个姿势调用 compile_to_executable()。
你会得到一个同名的可执行文件。
里面包含了所有被 @compile 装饰的函数。
如果你把函数名改成 main()。
它就会自动成为运行入口点。
你不会再在 Python 里点一下就跑。
你要手动去运行 build 目录里的那个文件。
你会发现这不是“把 C 引入 Python”。
而是“用 Python 写一个像 C 一样的程序”。
几乎所有 C 的特性,它都能生成得像模像样。
指针用 ptr[T]。
数组用 array[T, N]。
结构体、联合体、枚举,用修饰的 Python 类就能做出来。
算术运算符、控制流,都能照搬。
除了 goto 这种“古早狠活”。
switch/case?
用 match/case 替代。
但没有 default 回退。
可变长度数组呢?
C11 才有,编译器也不必定支持。
所以它暂时缺席。
你以为这就是全部。
实则它真正的杀手锏,在编译时代码生成。
把“危险的C”变成“有凭有据的C”
许多人害怕 C。
不是由于它难学。
而是由于它太自由,太容易“杀人”。
一不小心就是越界、悬垂、野指针、内存泄漏。
Cython 尝试用内存安全机制来兜底。
PythoC另辟蹊径。
它在类型系统里藏了两把锁。
第一把叫“线性类型”。
它能生成一个“证明”。
证明你做了内存分配。
释放那块内存时,这个证明必须被“消耗”。
如果每一次分配都没有匹配的 consume(prf)。
如果你忘了 prf=linear()。
编译器会在编译时把你拦下。
不是运行时出事后再追查。
是直接不让你过关。
你可以像文档里那样,造一个 lmalloc()/lfree() 的组合。
用线性类型自动帮你做那些繁琐的手工检查。
你依旧可以手动 malloc()/free()。
但“把风险往编译期挪”,正是它想给你的底气。
第二把叫“细化类型”。
你写一个检查函数,返回布尔值。
列如检查空指针。
你用 refine() 传入一个值。
你会得到一个“特定检查函数”的 refined[func] 类型。
编译器就能确保,这个值在返回之前,必须经过那道处理。
常见的检查,被聚焦在一个点做完。
你不用在每个分支都反复怀疑自己。
它不是替你写代码。
它是替你写证据。
证据链的终点,是一个更干净的 C 程序。
和 Cython 的分歧:各走一条路
你可能会说,Cython 也能在编译期生成代码。
是的。
但 PythoC 更像一个代码铸造厂。
它不执着于“加速 Python 调用”。
它想让你铸造出“不同类型签名”的 C 代码。
你写一个 make_point(T)。
它接受类型注解,列如 i32@type、f64@type。
它在编译时生成类 Point 和 add_points 的“类型特化版本”。
你用 suffix@type 告知编译器。
把生成对象的名字里带上类型信息。
Point 会变成 Point_i32 或 Point_i64。
这就是 C 里区分同名函数不同签名的老办法。
你还可以结合运行时分发,做一个简洁的多态。
这是一种“先把可能性在编译期铺开,再在运行时挑选”的模式。
Cython 的类型系统更像是“模拟 C 的行为”。
它不包含这些“证明驱动”的安全特性。
这不是谁更好。
而是两种哲学。
一个是把 Python 拉近 C。
另一个是用 Python 打造 C。
两种路,两个宇宙。
先不完美,但可能更自由
坦白说,PythoC 还很早期。
它每次运行都要重新编译。
这意味着你得接受那一小段的等待。
它还没有像 Cython 那样,能在 Python 调用时重用已编译代码的“缓存机制”。
但这也让它更纯粹。
它鼓励你拿它去生成一个独立的 C 程序。
像人手敲的一样。
只不过你用的是 Python 的语法。
未来呢?
也许它会加一个 @cached。
先编好,再在 Python 里复用。
也许它会更紧密地和 Python 的模块构建系统接轨。
也许它会始终坚持“生成独立 C”的原则。
谁知道呢。
早期的工具,总有一股“不被定义”的张力。
但你能看见它的野心。
它不是把速度带到你的桌面。
它是把选择权带到你的手里。
你要的是更快的 Python?
还是一个更像你的 C?
故事里的人,最后做了一个小实验。
他把一个老项目里那段“指针操作”的模块,用 PythoC 的类型注解重写。
他用 ptr[T] 去显式标注每一次指针传递。
他把数组改成 array[T, N],把越界检查放在一个 refine() 里。
他用线性类型去绑定每一次 lmalloc() 与 lfree() 的配对。
他编译它,跑它,拍了一张内存分析的截图。
没有泄漏。
没有悬垂。
没有调试到天亮。
他给同事发了一句消息。
“这不是更酷。”
“这是更稳。”
我们为什么要在乎一个还不完美的工具?
由于它把“编译时思维”带回了大众开发者的桌面。
它在提醒我们,越往后,工程的关键不是谁写得快。
而是谁把“风险前置”,谁把“复杂度可视化”,谁把“错误变成编译期的红线”。
我们常说,Python 让人写得舒服。
C 让机器跑得舒服。
PythoC 像是在问一个更直接的问题。
能不能让人写得舒服,同时让编译器替你把机器的“规矩”守好?
当一门语言去驱动另一门语言的生成。
当一个运行时去塑造另一个运行时的骨架。
技术就不再是“从这到那”的桥。
而是“把两边同时变得更清楚”的镜子。
我们不需要更多的魔法。
我们需要更好的证据。
我们不需要再去赌运气。
我们需要把风险变成规则。
不是赞歌。
而是邀请。
一切都还在变化。
但有一点已经很清楚。
真正的工具,不是把你变成高手。
而是让你不再害怕犯错。
你准备好用 Python 写一个你敢放心托付给 C 的程序了吗?


