PythoC:一种从 Python 生成 C 代码的新方法

导读:PythoC 允许开发者将 Python 作为 C 代码生成器,但它比 Cython 提供更多功能与灵活性。以下是对这款全新的 Python C 代码生成器做简单介绍。
Python 和 C 的共通之处比表面看起来要更多。
Python 解释器的参考版本便是用 C 语言编写的,许多为 Python 编写的第三方库也封装了C 代码。此外,还可以从 Python 生成 C 代码。
使用 Python 生成 C 代码通常需要像Cython这样的库,这些库使用类型注释的 Python 代码为 Python 生成 C 扩展模块。
现在,有一个名为PythoC 的新项目采用了不同的方法。它使用类型提示的 Python 以编程方式生成 C 代码——但主要用于独立用途,并且比 Cython 具有更多的编译时代码生成功能。
PythonC 的开发者用“C 级运行时,Python 驱动的编译时”来描述他们的开发理念。该项目仍处于早期阶段,但已有的功能足以值得开发者来关注。
一个基本的 Python 程序以下是一个根据 Python 示例改编的简单程序:
frompythocimportcompile, i32@compiledefadd(x: i32, y: i32) -> i32: returnx + yif__name__ =="__main__": print(add(10,20))
要指定模块中哪些函数需要编译成 C 代码,可以使用 PythonC 的@compile装饰器,并为结果和每个参数提供类型提示。
注意,你需要导入 PythonC 自带的 ` i32int` 类型提示,而不是使用 Python 原生的 `int` 类型int提示。这意味着使用的是机器原生的整数,而不是 Python 的任意大小整数。
运行此程序后,延迟一段时间后会得到输出结果。C 代码每次执行程序时30都会即时编译,因此会有些延迟。PythoC 目前还没有像 Cython 那样,在从 Python 调用时重用已编译代码的机制。
乍一看,这似乎是一个相当大的限制。但实际上,这正是它的优势所在:你可以将 PythoC 用作 C 程序的代码生成系统,生成独立运行的 C程序,而不是将 C模块导入 Python。
生成独立的 C 程序这是同一程序的全新版本,行为却有所不同。
frompythocimportcompile, i32, ptr, i8frompythoc.libc.stdioimportprintf@compiledefadd(x: i32, y: i32) -> i32: returnx + y@compiledefmain(argc: i32, argv: ptr[ptr[i8]]) -> i32: printf("%u\n", add(10,20))if__name__ =="__main__": frompythocimportcompile_to_executable compile_to_executable()
首先,你会注意到的是底部的代码块——这个compile_to_executable()函数正所谓恰如其名。调用它后,当前模块将被编译成一个同名的可执行文件,其中@compile包含所有被 `-` 装饰的函数。
另一个区别是,该函数现在与C 程序中的函数main()具有相同的名称。这意味着编译后的可执行文件将自动使用该名字作为其执行入口点。
最后,运行此程序时,生成的可执行文件(位于build子目录中)不会自动运行;我们须手动运行它。此程序的目的是构建一个独立的C 程序,其外观与用 C 语言手动编写的程序完全相同,但语法却与 Python 类似。
PythonC 对 C 语言特性的模拟除了少数例外情况外,PythoC 可以生成充分利用 C 语言特性集和运行时的代码。
我们已经了解了如何使用类型注解来指示基本数据类型。同样,可以使用ptr[T]注解来描述指针(如上所示),并用array[T, N]它来表示类型为 T 的 N 维数组。
我们可以通过修饰 Python 类来创建结构体、联合体和枚举,所有常用的运算符和控制流操作(除了 `__include__` goto)都适用。对于`__include__` switch/case,只需使用 `__include__` match/case,尽管它不支持回退情况。
另一个缺失的功能是可变长度数组。在 C 语言中,只有 C11 及更高版本才支持此功能,而且编译器对它的支持是可选的,因此 PythonC 目前不支持它也并不奇怪。
编译时代码生成Cython 可以用于编译时代码生成,这表示着您可以生成不同类型的 C 代码,甚至可以根据编译时的情况回退到 Python 代码。但是 PythonC 的编译时代码生成功能是 Cython 所不具备的。
以下是 Python 文档中的一个代码示例:
from pythocimportcompile, struct, i32, f64def make_point(T): @struct(suffix=T) classPoint: x:T y:T @compile(suffix=T) def add_points(p1:Point, p2:Point) ->Point: result:Point=Point() result.x = p1.x + p2.x result.y = p1.y + p2.y returnresult returnPoint, add_pointsPoint_i32, add_i32 = make_point(i32)Point_f64, add_f64 = make_point(f64)
该make_point(T)函数接受类型注解(i32`@ type`、 f64`@type`),并在编译时生成类Point和add_points函数的类型特化版本。` suffix@type` 参数@compile表示“更改生成对象的名称,使其名称中包含类型信息”——例如,`@type`Point会变成 ` @type`Point_i32和 `@ Point_i64type`,这在 C 语言中是区分同一函数的不同类型签名的一种方法。
此外,还可以结合运行时分发来实现多态性。
内存安全功能C语言手动内存管理可能引发的各种bug,对于任何使用过这门语言的人来说都耳熟能详。Cython提供了内存安全机制来解决这个问题,而Python则提供了独特的基于类型的特性。
其中一项特性称为linear(线性类型)。linear导入该类型可以生成一个“证明”,通常用于内存分配,该证明必须在释放同一块内存时被“消耗”。如果consume(prf)每个内存分配都没有匹配的证明prf=linear(),PythonC 编译器将生成一个编译时错误。上面链接的文档展示了如何创建简单的lmalloc()`/`lfree()函数来安全地分配和释放内存。虽然没有规定你必须使用线性类型而不能手动使用malloc()`/ free()`,但线性类型可以自动执行许多手动检查,并将检查集中在编译时而不是运行时。
另一种基于类型的安全特性是细化类型。其思想是,你可以定义一个函数来执行某种检查(例如,检查空指针),并返回一个布尔值结果。然后,你可以使用该refine()函数传递一个值,并获取该函数特有的类型refined[func]。这使得编译器能够确保该类型在返回之前必须经过某种处理,并且允许在代码的单个位置处理常见的检查(例如,检查非空指针)。
Cython 的类型系统主要用于直接模拟 C 语言的行为,因此不包含类似的功能。
PythonC 的未来发展PythoC 目前仍处于早期开发阶段,其未来的发展方向相对开放。
一种可能性是,它可以在运行时与 Python 更紧密地集成。例如,@cached装饰器可以预先编译模块一次,然后在 Python 内部调用时重用已编译的模块,而无需每次运行时都重新编译。
当然,这还需要与 Python 现有的模块构建系统集成。虽然这种程度的集成可能并非该项目的目标,但它将使 PythoC 对那些需要集成 C 和 Python 的开发者来说更加实用。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。
