- 开发工具
【免费下载链接】cangjie_python_interop
本文围绕官方文档 仓颉 Python 互操作库 API 展开,系统介绍 cangjie_python_interop 仓库中py_interop包的定位、平台约束、API 组织方式与源码文件结构。读完后,你将掌握:该库在 Linux + cjnative 后端下的使用前提、Python解释器类与PyObj类型族的能力边界、Python 回调仓颉函数的实现机制,以及如何构建、安装并运行仓库自带的完整测试套件。
一、库的定位:为什么仓颉需要一个 Python 互操作库
官方 API 总览文档开宗明义:
为了兼容强大的计算和 AI 生态,仓颉支持与 Python 语言的互操作调用,仓颉-Python 互操作库为用户提供该能力。
目前 Python 互操作仅支持在 Linux 平台使用,并且仅支持仓颉编译器的 cjnative 后端。
这两句话是整个库的核心约束,直接使用前的必要前提可以归纳为:
| 约束项 | 说明 | 依据 |
|---|---|---|
| 运行平台 | Linux 平台 | API 总览、README.md 的 Constraints 一节 |
| 编译器后端 | 仅支持仓颉编译器 cjnative 后端 | 同上;cjpm.toml 中output-type = "dynamic"表明产物为动态库 |
| 依赖运行时 | 依赖 Python 官方动态库libpython3.x.so,推荐 Python 3.9.2,支持 3.0 以上版本 | 用户指南 动态库加载策略一节 |
从源码结构看,平台差异是通过条件编译处理的:python.cj 中以@When[py_interop_plat == "ohos"]引入ohos.hilog,而 cjpm.toml 的[target]配置中,aarch64-linux-ohos目标携带--cfg "py_interop_plat = ohos",x86_64-unknown-linux-gnu目标携带--cfg "py_interop_plat = Linux",可以推断同一套源码同时面向 Linux 与 OpenHarmony 两个宿主构建,但当前对外承诺的可用平台仍是 Linux。
用户指南 docs/user-guide.md 中最小的完整用例,展示了该库最典型的用法——导入 Python 标准库sys模块并读取其version属性:
import py_interop.* main(): Int64 { Python.load() var sys: PyObj = Python.importMod("sys") println(sys["version"]) Python.unload() return 0 }执行结果形如:
3.10.12 (main, May 27 2025, 17:12:29) [GCC 11.4.0]此外用户指南特别提醒:执行 Python 互操作相关代码前,建议将仓颉默认栈大小配置为至少 1MB(export cjStackSize=1MB),因为 Python 互操作会调用大量 Python C API native 代码,这部分代码在仓颉侧无法进行栈保护,默认 64KB 栈大小下容易发生溢出。
二、包列表:库只包含一个 py_interop 包
官方总览文档给出的包列表如下(继承自 libs_overview.md):
| 包名 | 功能 |
|---|---|
| py_interop | py_interop 包提供仓颉与 Python 跨语言互操作能力。 |
仓库源码与这一声明完全对应:源码目录 src/ffi/py_interop/ 下唯一的包配置文件 cjpm.toml 声明了包名与产物类型:
[package] name = "py_interop" output-type = "dynamic" src-dir = "." target-dir = "../../../target" version = "1.0.0"output-type = "dynamic"说明该包以共享库形式产出(.cjo+.so),这也解释了它需要与 C 侧libpython3.x.so动态链接、通过 FFI 调用 C API 的技术路线。
py_interop 包提供的三类核心能力
包总览文档 进一步把包能力归纳为三点,这也是后续所有 API 的组织主线:
- 全局解释器对象
Python(源码中类型为Python类):加载/卸载解释器资源、获取当前 Python 版本、导入并使用 Python 标准库/三方模块; - Python 数据类型与仓颉类型的互相转换:
PyBool/PyLong/PyFloat/PyString/PyTuple/PyList/PyDict/PySet均继承自PyObj,PyObj提供成员访问、函数调用、到仓颉字符串转换等通用接口; - Python 代码回调仓颉函数:通过
PyCFunc把仓颉函数注册为 Python 侧的 C 风格函数。回调经 C 作为中间层完成,并用到 Python 三方库ctypes与_ctypes。
注意:该包暂不支持 Windows 平台(引自 包总览文档)。
API 全景:常量、函数、接口、类、枚举、结构体与异常
包总览文档按 API 类别给出了完整清单,以下按原类别组织(链接已换算为仓库根路径):
常量 & 变量(详见 常量文档)
| 变量/常量名 | 功能 |
|---|---|
PYLOG | py_interop日志对象,用于打印控制及输出 |
Python | Python 解释器全局资源 |
函数(详见 函数文档)
| 函数名 | 功能 |
|---|---|
CJ_init_args_processor() | 初始化参数处理器 |
CJ_release_args_processor() | 释放参数处理器 |
parseCallBackArgAsPyBool/PyDict/PyFloat/PyList/PyLong/PySet/PySlice/PyString/PyTuple(PyObj) | 判断回调函数的某个入参是否为对应类型;是则返回对应Py*对象,否则抛出PythonException |
parseCallBackArgsList(CPointer<Unit>) | 将回调函数的入参解析为PyList<PyObj>类型的参数列表 |
接口:CjObj(详见 接口文档),提供将仓颉类型转换为 Python Object 的函数。
类(详见 类文档)
| 类型名 | 功能 |
|---|---|
PyBool | 表示 Python 的布尔类型 |
PyCFunc | 表示 Python 的 C 风格函数类型 |
PyDict<K, V> | 表示 Python 的字典类型 |
PyFloat | 表示 Python 的浮点数类型 |
PyList<T> | 表示 Python 的列表类型 |
PyLong | 表示 Python 的整数类型 |
PyObj | 表示 Python 所有类型的父类 |
PyObjIterator | 表示PyObj的迭代器类型 |
PySet<T> | 表示 Python 的集合类型 |
PySlice<T> | 表示 Python 的区间类型 |
PyString | 表示 Python 的字符串类型 |
PyTuple | 表示 Python 的元组类型 |
PythonBuiltins | 表示 Python 的内建函数集合 |
PythonLogger | 表示py_interop包的日志类型 |
枚举(详见 枚举文档):LogLevel(日志等级)、PyCType(ctypes 枚举)。
结构体(详见 结构体文档):Version(Python 版本号)。
异常类(详见 异常文档):PythonException。
三、源码结构:每个 API 类落在哪个 .cj 文件
py_interop包的全部源码位于 src/ffi/py_interop/,共 14 个.cj文件、约 3900 行。把 API 清单与源文件对应起来,可以清晰地看到库的内部分层:
| 源文件 | 承载的 API / 职责 |
|---|---|
| python.cj | 全局资源层:Version结构体(L57-L88)、PythonException、LogLevel、PythonLogger(即PYLOG)、解释器类Python(L403) |
| pyobj.cj | PyObj基类:成员访问、()调用、运算符重载、asPyXxx类型转换等通用接口(578 行,包内最大文件) |
| pybase.cj | 基础标量类型:PyBool、PyLong、PyFloat、PyString,含各自与 C API 的转换函数 |
| pylist.cj / pytuple.cj / pydict.cj / pyset.cj / pyslice.cj | 容器与区间类型:PyList、PyTuple、PyDict、PySet、PySlice |
| pymodule.cj | 内部类PyModule:封装PyImport_ImportModule与模块__dict__读取,是Python.importMod()的底层实现 |
| pycfunc.cj | PyCFunc与 Python 回调仓颉函数的 C 中间层实现 |
| cjobj.cj | CjObj接口及对Bool/Int*/UInt*/Float*/String/Array/HashMap/HashSet的toPyObj扩展 |
| pythoncapi.cj / pythoncwapper.cj | Python C API 的 foreign 声明与 C 侧包装函数 |
| slab.cj | 支撑PyCFunc回调注册的多线程 slab 分配器 |
几个值得关注的实现细节:
1.Python.load()的加载流程。python.cj 中load依次执行:initLock互斥保护 → 路径校验 →CJ_BindOSThread()绑定系统线程 →loadSo加载动态库 →readSym读取符号 →Py_Initialize()初始化解释器 →PyEval_SaveThread()释放 GIL 并保存线程状态 → 通过setPyArgs()把仓颉侧命令行参数传给 Python 的__main__(对应 C APIPySys_SetArgv)→ 取回 builtins 字典。失败路径统一抛出PythonException,这与用户指南中“load函数失败时会抛出PythonException异常,请注意try-catch”的描述一致。unload()则对称地执行Py_FinalizeEx()、解绑线程并关闭动态库句柄。
2. 动态库三级查找策略。getPythonPath() 按顺序尝试:
- 环境变量
PYTHON_DYNLIB(getPythonPathByEnv); python3可执行文件的动态库依赖(Linux 上执行ldd $(which python3),见 L41-L44);- 系统默认路径
/lib/、/usr/lib/、/usr/local/lib/(getPythonPathBySysLib)。
查找动态库名时,findLibIn() 从TOP_MOJOR_VERSION.TOP_MINOR_VERSION(源码中为 3.12)向下逐个次版本号探测,后缀支持.so、d.so、m.so、dm.so,且明确不支持 2.X。用户指南中给出的推荐版本 3.9.2 即在此探测范围内。
3.eval()与exec()的底层差异。两者最终都调用pyRunString(L863-L876),区别在 mode 参数:eval使用pyEvalInput = 258(等价 C 宏Py_eval_input),exec使用PyFileInput = 257。源码中还可见eval的输入长度限制cmd.size > 1000会直接返回不可用对象,对应用户指南“cmd字符串长度必须在 (0, 1000]”的规格说明。
4. 回调机制:仓颉函数如何成为 Python 可调用对象。pycfunc.cj 的做法是:把用户传入的(Array<PyObj>) -> PyObj闭包存入静态 slab(SlabMT),取回一个整型 id,再用PyCFunction_New把 C 函数py_invoke_callback包装成 Python 侧的 C function 对象,并将 id 作为self传入;Python 端调用时,py_invoke_callback(L26-L52)解析出 id 与参数元组,再从 slab 中取出原始仓颉函数执行。若仓颉回调抛出异常,源码会把仓颉异常信息与堆栈逐帧拼接到msg中,经Python.setPyErr写入 Python 的RuntimeError,从而在 Python 堆栈中一并呈现——这正是用户指南“Python 回调仓颉的异常信息”一节所述行为的实现。
5. 类型转换经由 C API 一对一封装。以 pybase.cj 为例:PyBool用PyBool_FromLong/PyBool_Check,PyLong用PyLong_FromLongLong/PyLong_AsLongLong(溢出判定与ERRCODE_INT = -1配合),PyFloat用PyFloat_FromDouble/PyFloat_AsDouble,PyString用PyUnicode_FromString/PyUnicode_AsUTF8。每个私有转换函数都遵循getGIL()→ 调用 C API →releaseGIL()的锁配对模式,可印证用户指南“全局解释器锁(GIL)确保并发访问安全”的说法。
四、类型映射关系:仓颉类型与 Python 类型如何互转
用户指南的类型映射表 是py_interop包的第二条主线,完整继承如下。
仓颉类型到 Python 类型映射:
| Cangjie Type | Bridge Type | Python Type |
|---|---|---|
Bool | PyBool | bool |
UInt8/Int8/Int16/UInt16/Int32/UInt32/Int64/UInt64 | PyLong | int |
Float32/Float64 | PyFloat | float |
Rune/String | PyString | str |
| - | PyTuple | tuple |
Array<T> | PyList<T> | list |
HashMap<K, V> | PyDict<K, V> | dict |
HashSet<T> | PySet<T> | set |
Range<T> | PySlice<T> | slice |
(Array<PyObj>) -> PyObj | PyCFunc | builtin_function_or_method |
Python 类型到仓颉类型映射:
| Python Type | Bridge Type | Cangjie Type |
|---|---|---|
bool | PyBool | Bool |
int | PyLong | Int64/UInt64 |
float | PyFloat | Float64 |
str | PyString | String |
tuple | PyTuple | Array<PyObj> |
list | PyList<PyObj> | Array<PyObj> |
dict | PyDict<PyObj, PyObj> | HashMap<PyObj, PyObj> |
set | PySet<PyObj> | HashSet<PyObj> |
slice | PySlice<Int64> | Range<Int64> |
使用注意(引自用户指南):仓颉侧泛型需约束为PyFFIType接口的子类,包括PyObj类及CjObj接口的子类;仓颉侧可通过构造Array<PyObj>数组来创建PyTuple对象;Python 侧的自定义类实例、函数与可调用对象、模块与命名空间等,均以PyObj形式传递至仓颉侧。CjObj接口的toPyObj扩展实现位于 cjobj.cj。
五、使用指导:动态库加载策略与模块路径配置
总览文档 的“使用指导”一节指向 用户指南 与 开发者指南,两者分别面向“用库”和“构库”。以下保留其中对实操最关键的部分。
加载/卸载解释器的完整语义
Python解释器类对外提供四个静态接口(代码原型引自用户指南):
public class Python { public static func load(loglevel!: LogLevel = LogLevel.WARN): Unit public static func load(path: String, loglevel!: LogLevel = LogLevel.WARN): Unit public static func isLoad(): Bool public static func unload(): Unit }关键使用约束:
load提供可选参数配置PythonLogger打印等级,不配置时会把PYLOG重置为WARN;load(path)的path必须指定到动态库文件(如/usr/lib/libpython3.9.so),不可为目录;- 加载/卸载仅需调用一次且一一对应,多次调用仅第一次生效;
- 程序运行时全局只会加载一个 Python 解释器;
unload未调用会造成资源泄漏(源码中unload负责Py_FinalizeEx、解绑线程与关闭句柄,见 python.cj L527-L554)。
若从源码编译获取动态库,用户指南推荐:
# 在Python源码路径下: ./configure --enable-shared --with-system-ffi --prefix=/usr make make install注意:未使用--enable-shared编译的 Python 可执行文件不依赖动态库,无法走“可执行文件依赖”这一查找分支,此时需要PYTHON_DYNLIB或系统默认路径可用。开启LogLevel.INFO后可观察完整的三级查找日志,查找失败时抛出PythonException: Can't get path from system lib path, load exit.。
模块导入与路径配置
Python.importMod(module)导入标准库/三方模块并返回模块对象;找不到模块时抛出仓颉异常(源码实现见 pymodule.cj L34-L47,失败时throw PythonException("Import \"${name}\" module failed..."))。模块路径配置规则:
- 内置模块(
sys、time)无需设置路径;标准库模块(os、json)需正确设置 Python 安装路径。Linux 默认安装路径为/usr无需手动设置; - 三方/自定义模块(如
numpy)有两种方式:设置PYTHONPATH环境变量,或运行时通过PyList.insert动态修改sys.path后再次importMod。
并发使用要点
程序运行时全局仅加载一个 Python 解释器,通过 GIL 保证并发访问安全。该库支持系统线程间并发,但不支持仓颉协程间直接并发:在spawn协程中调用互操作接口前,需通过CJ_BindOSThread将协程绑定到系统线程,调用结束后CJ_UnbindOSThread;未绑定的spawn调用期间可能因线程切换抛出仓颉异常,且同时拉起超过 8 个绑定线程的spawn可能出现死锁(仓颉运行时已知问题)。Python.load()内部已为主线程自动完成绑定(对应 python.cj L472-L474 的CJ_BindOSThread调用)。
六、构建、安装与测试:仓库的实战闭环
总览文档 指向的 开发者指南 给出了完整的构建流程,保留如下:
构建准备
- 需要 Linux 平台的
cangjie SDK,并执行对应 SDK 的envsetup脚本完成配置; - 使用
python构建脚本方式编译需要安装python3。
构建步骤
# 1. 克隆本项目后,进入 build 目录编译 cd build python3 build.py build -t release构建脚本为 build/build.py,支持debug、release两种编译类型(-t或--build-type指定)。
# 2. 安装到指定目录(默认 target 目录) python3 build.py install python3 build.py install --prefix ./output # 3. 清理编译中间产物 python3 build.py clean产物目录结构:
output/ |-- py_interop.cjo # cjo文件 |-- libpy_interop.so # 共享库文件build.py build的额外选项:--target TARGET(默认native,当前仅支持 Linux 平台编译)、-t/--build-type(debug/release)、-h/--help。
编译完成后,在仓颉工程中按用户指南的链接方式引用即可(-L指向库目录、--import-path指向.cjo所在目录):
cjc -l py_interop -L /path/to/py_interop --import-path /path/to/py_interop ./main.cj -o ./main && ./main测试体系
仓库自带完整测试套件,位于 tests/,由 tests/test.sh 驱动:
- 测试基于 cangjie_test_framework 编写,需先编译
py_interop库,将产物py_interop.cjo与libpy_interop.so拷贝到$CANGJIE_STDX_PATH/../../../py_interop目录,并把该目录加入LD_LIBRARY_PATH; - 运行方式:
python3 tests/test.sh <cangjie_test_framework 所在目录>,底层以--retry=3 --timeout=600 -j16等参数执行 tests/testlist 列出的用例; - tests/test.cfg 中可见运行条件为
LLVM/x86/Linux/Unix,编译命令通过pyffi-link = -l py_interop -L $CANGJIE_STDX_PATH/../../../py_interop --import-path ...链接库; - 用例目录按功能组织,与 API 能力一一对应:
eval/、exec/、import/、global/(解释器加载与日志,含 load01~load20、log01~log04)、index/、iterator/、kargs_call/、object/(各Py*类型构造与操作)、object_operation/(算术/比较运算符)、slice/、cfunc/(回调,含 spawn 场景)、compare/、global/load13/(C 共享库加载)。
这些测试与用户指南中各示例代码相互印证,可视为“文档示例可运行”的回归保障。
七、小结:从总览到落地
回到 libs_overview.md 给出的三要素,可以形成一张落地清单:
- 确认环境:Linux + cjnative 后端 + 共享编译的
libpython3.x.so(推荐 3.9.2)+cjStackSize=1MB; - 理解包结构:库只有一个
py_interop包,API 按“全局资源(PYLOG/Python)→ 类型族(PyObj及其子类)→ 回调(PyCFunc)→ 支撑类型(CjObj/Version/LogLevel/PythonException)组织,源码文件与之一一对应; - 按指南行动:用库读 用户指南,构库/装库/测试读 开发者指南,查符号签名读 py_interop 包 API 文档。
- 开发工具
【免费下载链接】cangjie_python_interop
相关推荐
JupyterLab Monorepo 代码总览:仓库结构、TypeScript 包与 Python 包开发工作流全解析
JupyterLab Monorepo 代码总览:仓库结构、TypeScript 包与 Python 包开发工作流全解析 本篇技术指南基于 docs/sourc
前端后端数据科学开发工具Eclipse Theia 代码组织结构解析:common / browser / node / electron 目录分层与跨平台约束
Eclipse Theia 代码组织结构解析:common / browser / node / electron 目录分层与跨平台约束 Eclipse The
IDE代码编辑器开发工具前端桌面应用插件系统后端AI 应用Jib 代码风格指南全解析:Java 编码规范、格式化工装与包结构约束
Jib 代码风格指南全解析:Java 编码规范、格式化工装与包结构约束 导读 STYLE_GUIDE.md https://link.gitcode.com/i
开发工具构建工具云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考