☰
cangjie_python_interop 仓颉-Python 互操作库:跨语言桥接能力与源码级实战指南
2026/10/14 16:55:11 网站建设 项目流程
  • 开发工具

【免费下载链接】cangjie_python_interop

项目地址:https://gitcode.com/Cangjie/cangjie_python_interop
点击查看免费下载

本文围绕cangjie_python_interop仓库的项目说明文档展开,系统介绍仓颉(Cangjie)与 Python 跨语言互操作库py_interop的定位、核心能力、平台约束与源码构建流程,并结合仓库源码与测试用例说明其解释器加载、模块导入、表达式执行、类型映射及 Python 回调仓颉函数的实现要点。读完后,你将掌握如何在 Linux 环境下编译该库、通过Python解释器类操作 Python 对象,以及构建产物与测试的运行方式。

项目定位:为计算与 AI 生态打通仓颉与 Python 的桥梁

根据项目文档 README.md 与 README_zh.md 的介绍,仓颉-Python 互操作库旨在提供仓颉与 Python 之间的跨语言互操作能力,以兼容强大的计算和 AI 生态。该库通过py_interop包向仓颉侧用户提供以下核心能力:

  • 类型互相转换:Python 数据类型与仓颉类型之间的双向转换,如PyLong与Int64/UInt64、PyString与String、PyDict<K,V>与HashMap<K,V>等;
  • 导入并使用 Python 模块:通过仓颉语言导入 Python 标准库、三方模块及用户自定义模块,并访问模块内的函数、变量、类等可见对象;
  • 执行 Python 表达式与代码:eval()执行单个表达式并返回值,exec()执行完整代码块;
  • Python 调用仓颉函数:将仓颉CFunc函数封装为PyCFunc对象传递给 Python 侧调用。

项目将详细使用文档放在docs目录:用户指南 覆盖解释器接口、PyObject类型体系、并发与异常处理等规格说明;开发者指南 覆盖构建准备、编译、安装与清理步骤。

一个最小的使用示例(引自用户指南)展示了从加载解释器到读取sys.version的完整流程:

import py_interop.* main(): Int64 { Python.load() var sys: PyObj = Python.importMod("sys") println(sys["version"]) Python.unload() return 0 }

执行结果为当前环境的 Python 版本,例如3.10.12 (main, May 27 2025, 17:12:29) [GCC 11.4.0]。这个例子即点出了整个库的使用主线:Python.load()初始化 → 通过Python.importMod/Python.eval/Python.exec使用 Python 能力 →Python.unload()释放资源。

平台约束与适用前提

项目文档明确给出了当前的平台约束,这是使用前的必要前提:

  • 仅支持Linux 平台编译和使用(用户指南同时提及 OpenHarmony 平台支持);
  • 仅支持仓颉编译器的cjnative 后端;
  • 程序运行前,仓颉默认栈大小需配置为至少1MB(export cjStackSize=1MB)。

关于栈大小要求,用户指南的解释是:Python 互操作会大量调用 Python C 库的 native 代码,这部分代码在仓颉侧无法进行栈保护;仓颉默认栈保护大小为 64KB,在调用 Python C API 过程中容易超出默认栈大小引发溢出,导致不可预期结果。测试配置 tests/test.cfg 中对运行环境的要求(如LD_LIBRARY_PATH注入)也印证了这一点:

[env] LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH

仓库目录结构

项目文档给出的顶层目录结构如下:

cangjie_interop/ ├── build # 构建脚本 ├── docs # 介绍文档,包括用户指南、开发者指南、api参考 ├── src │ ├── ffi │ │ └── py_interop # 仓颉-Python 互操作库源码 └── tests # 测试用例

对照当前仓库实际内容,各目录职责清晰:

  • src/ffi/py_interop/ 是库源码主体,全部为.cj仓颉源文件与一份包配置文件 cjpm.toml;
  • docs/ 除用户指南与开发者指南外,还包含 API 参考文档,如 py_interop_package_overview.md 以及按类别拆分的类、函数、枚举、异常等参考文件(docs/libs/py_interop/py_interop_package_api/目录下)。

从源码结构看,库的模块划分与 Python C API 的对象体系一一对应,共 13 个源文件:

源文件职责(从源码注释与内容看)
python.cj全局解释器类Python、Version版本结构、PythonException异常、日志PYLOG等全局资源
pythoncapi.cj对 Python C API 的foreign声明与封装
pythoncwapper.cjC 接口包装层
pyobj.cj通用PyObj类型:成员访问、调用、算术运算符重载等
pybase.cjPyBool、PyLong、PyFloat、PyString等基础数值/字符串类型
pytuple.cj、pylist.cj、pydict.cj、pyset.cj、pyslice.cj集合类 Python 类型封装
pymodule.cjPython 模块导入(PyImport_ImportModule)与模块字典访问
pycfunc.cjPython 回调仓颉函数的桥接(PyMethodDef、回调参数解析)
cjobj.cjCjObj接口:仓颉类型toPyObj()扩展
slab.cj引用(Reference)内存管理相关基础设施

包配置 cjpm.toml 揭示了几个关键工程事实:

[package] name = "py_interop" output-type = "dynamic" # 产物为动态库 target-dir = "../../../target" version = "1.0.0" cjc-version = "0.53.21"
  • output-type = "dynamic"表明该库编译产物是动态库(.so)加仓颉包文件(.cjo),与开发者指南中output/目录下的py_interop.cjo、libpy_interop.so一致;
  • 编译目标覆盖x86_64-unknown-linux-gnu以及aarch64-linux-ohos、x86_64-linux-ohos平台,Linux 目标通过--cfg "py_interop_plat = Linux"注入平台配置,OHOS 目标则通过--cfg "py_interop_plat = ohos"并链接 OHOS sysroot。从 python.cj 中@When[py_interop_plat == "ohos"] import ohos.hilog.*等条件编译代码可以印证:同一套源码通过 cfg 开关同时适配 Linux 与 OpenHarmony。

构建与安装

构建准备

根据 开发者指南:

  1. 准备 Linux 平台的cangjie SDK,并执行对应 SDK 的envsetup脚本,确保 SDK 被正确配置;
  2. 使用python构建脚本方式编译,需要安装python3。

编译、安装与清理

# 克隆本项目后,进入 build 目录编译 cd build python3 build.py build -t release

当前支持debug、release两种编译类型,通过-t或--build-type指定。

安装到指定目录:

python3 build.py install # 默认安装到 target/ 目录 python3 build.py install --prefix ./output # 指定安装目录

编译产物目录结构:

output/ |-- py_interop.cjo # cjo 包文件 |-- libpy_interop.so # 共享库文件

清理编译中间产物:

python3 build.py clean

build.py提供的更多构建选项:

选项说明
--target TARGET指定编译目标产物的运行平台,默认native(本地平台),当前仅支持 Linux 平台编译
-t, --build-type BUILD_TYPE指定构建产物版本,可选值debug/release
install [--prefix PREFIX]将构建产物安装到指定路径,默认为target/;需先正确执行build
clean清除默认路径的构建产物
-h, --help打印帮助信息

测试运行方式

仓库 tests/ 目录内置了完整的测试用例,按功能划分为cfunc、compare、eval、exec、global、import、index、iterator、kargs_call、object、object_operation、slice等子目录。测试基于cangjie_test_framework框架编写,运行方式为(见 tests/test.sh):

# 在当前(tests)目录运行,参数为 cangjie_test_framework 代码所在目录 python3 "${FRAMEWORK_DIR}/cangjie_test_framework/main.py" \ --fail_exit --keep_temp --temp_dir=./test_temp --retry=3 \ --timeout=600 --test_cfg=./test.cfg ./ \ --test_list=./testlist -pFAIL --fail-verbose -j16 --level=0

前提条件(由脚本注释说明):需提前编译py_interop库,将产物py_interop.cjo和libpy_interop.so拷贝到$CANGJIE_STDX_PATH/../../../py_interop目录,并将该路径export到LD_LIBRARY_PATH环境变量。测试用例中的编译行使用-l py_interop -L ... --import-path ...链接该库(见 tests/test.cfg 的pyffi-link内部变量),例如 tests/py_interop/eval/eval01.cj 中通过// EXEC:注释行声明编译与运行参数,并设置了export cjStackSize=1MB——与用户指南中的栈大小建议相互印证。

核心能力与源码印证

以下能力均出自 用户指南 的规格说明,并结合仓库源码说明其落点。

解释器加载:Python.load()与动态库查找策略

Python类提供静态接口load(loglevel!)、load(path: String, loglevel!)、isLoad()、unload()。关键使用说明:

  • load支持无参加载与指定动态库路径加载两种方式;path必须指向动态库文件(如/usr/lib/libpython3.9.so),不能是目录或非动态库文件;
  • load失败时抛出PythonException,需要继续执行时应使用try-catch;
  • 加载和卸载仅需调用一次且一一对应,多次调用仅第一次生效;程序运行时全局只加载一个 Python 解释器;
  • 不配置loglevel时,PYLOG会重置为WARN等级。

无参load()的 libpython 动态库自动查找策略(推荐 Python 3.9.2,支持高于 3.0 且小于等于 3.10 的版本):

  1. 环境变量PYTHON_DYNLIB指定动态库路径;
  2. 若未指定,通过ldd $(which python3)从python3可执行文件的动态库依赖中查找(要求 Python 以--enable-shared方式编译);
  3. 若仍找不到,在系统默认动态库路径["/lib", "/usr/lib", "/usr/local/lib"]中按libpythonX.Y(.d|.m|.dm|.so)命名规则查找。

用户指南给出了完整的源码编译 Python 共享库的命令:

./configure --enable-shared --with-system-ffi --prefix=/usr make make install

在 python.cj 中可以看到该策略的代码痕迹:cmdExeDep = "ldd $(which python3)"常量、suffixOfDy = "so"后缀常量,以及保护加载/卸载的全局可重入锁initLock = ReentrantMutex()。测试目录 tests/py_interop/global/common.cj 还实现了一个辅助函数getPythonPathBySysLib,在/lib/、/usr/lib/、/usr/local/lib/中按libpython3.x前缀搜索动态库——这正是系统路径查找策略的测试侧实现。

模块导入:importMod()与模块路径配置

importMod(module: String): PyModule导入 Python 内置标准库模块、三方模块或用户自定义模块;模块不存在时抛出仓颉异常(如PythonException: Import "xxxx" module failed. / ModuleNotFoundError: No module named 'xxxx')。源码上对应 pymodule.cj 中对PyImport_ImportModule的调用及模块字典获取。

模块路径配置规则:

  • 内置模块(如sys、time)无需设置路径;标准库模块(如os、json)需要正确的 Python 安装路径——Linux 默认安装路径为/usr无需手动设置,OpenHarmony 需设置PYTHONHOME环境变量指向安装路径;
  • 三方模块与用户自定义模块(如numpy)需显式配置搜索路径,方法一为设置PYTHONPATH环境变量,方法二为运行时动态修改sys.path:
let sys = Python.importMod("sys") var path = (sys["path"] as PyList<PyObj>).getOrThrow() path.insert(0, PyString("/custom/path")) let test = Python.importMod("test")

仓库测试 tests/py_interop/import/importTest.py 提供了被导入模块的样例(函数、模块变量、类定义),tests/py_interop/import/下import01.cj~import09.cj覆盖了标准库、三方模块、自定义模块等多种导入场景。

表达式执行:eval()与代码执行:exec()

  • eval(cmd: String, module!: String = "__main__"): PyObj执行单个 Python 表达式并返回值;cmd长度需在 (0, 1000];仅支持表达式,赋值、循环、函数定义等语句会抛出SyntaxError异常;通过module参数可访问指定模块作用域内的变量与函数,例如Python.eval("add(a, b)", module: "evalTest")返回 30;
  • exec(code: String): Unit执行一段完整 Python 代码块,支持函数/类定义、import语句、多行流程控制语句等;执行出错时抛出异常。

测试用例 tests/py_interop/eval/ 目录中的eval01.cj~eval03.cj与 tests/py_interop/exec/ 中的exec01.cj、exec02.cj分别验证了正常执行、语法错误、模块不存在等分支行为(eval01.cj断言扫描SyntaxError、ModuleNotFoundError等关键字)。

Python类还提供getVersion(): Version返回当前 Python 版本,以及getBuiltinsAttr(attr: String): Option<PyObj>获取 Python 内置函数/对象(如type),未找到时返回None并打印 warn 日志。

全局日志PYLOG与版本信息Version

全局日志对象PYLOG(PythonLogger实例)提供debug/info/warn/error/log接口,默认等级为LogLevel.WARN,不支持 log 转储文件,load时可传入loglevel参数调整。源码位于 python.cj,LogLevel枚举含ERROR/WARN/INFO/DEBUG四级;日志测试见tests/py_interop/global/log01.cj~log04.cj。

Version结构由major/minor/micro三个Int64组成,仅在构造时赋值、之后不可修改,提供getVersion()(返回三元组)与toString()接口,其实现可对照 python.cj 中的public struct Version <: ToString。

PyObject类型体系与类型映射

互操作基于 Python C API 开发,数据交换统一通过PyObject结构体指针完成。仓颉侧将其封装为父类PyObj,由PyBool、PyLong、PyFloat、PyString、PyTuple、PyList、PyDict、PySet、PySlice等子类继承,对应源码文件即上文所列pybase.cj、pyobj.cj等。

官方类型映射表(仓颉 → Python):

Cangjie TypeBridge TypePython Type
BoolPyBoolbool
UInt8/Int8/Int16/UInt16/Int32/UInt32/Int64/UInt64PyLongint
Float32/Float64PyFloatfloat
Rune/StringPyStringstr
-PyTupletuple
Array<T>PyList<T>list
HashMap<K, V>PyDict<K, V>dict
HashSet<T>PySet<T>set
Range<T>PySlice<T>slice
(Array<PyObj>) -> PyObjPyCFuncbuiltin_function_or_method

Python → 仓颉方向:bool→Bool、int→Int64/UInt64、float→Float64、str→String、tuple/list→Array<PyObj>、dict→HashMap<PyObj, PyObj>、set→HashSet<PyObj>、slice→Range<Int64>。

几个关键实现细节(均可在源码中找到对应):

  • PyObj不提供对外构造函数,不允许在包外继承;isAvailable()判断封装的 C 指针是否为NULL;
  • 成员访问obj["key"]/ 赋值obj["key"] = value,赋值不可用对象(isAvailable()为false)时会删除该key;
  • ()运算符重载支持无参、位置参数(Array<PyObj>或Array<CjObj>,二者不可混用)与命名参数(HashMap<String, PyObj>作为 kargs)三种调用形式;
  • +、-、*、/、**、%二元运算符重载的合法类型组合遵循 Python 语义,错误情况打印 warn 并返回不可用PyObj;
  • asPyLong()/asPyFloat()/asPyString()/asPyBool()/asPyTuple()/asPyDict()/asPyList()/asPySet()/asPySlice()用于向下转型,类型不符时抛出异常;
  • CjObj接口为仓颉基础类型(Bool、Int8~UInt64、Float32/Float64、String、Array<T>、HashMap<K,V>、HashSet<T>)扩展了toPyObj(),实现仓颉 → Python 的便捷构造,见 cjobj.cj 与测试tests/py_interop/object/types.cj。

从 pybase.cj 的PyBool实现可以看到典型的桥接模式:构造时先getGIL()获取全局解释器锁,调用 C APIPyBool_FromLong生成 new reference,再releaseGIL;转回仓颉时pyBoolCheck比较是否为Py_TrueStruct。所有涉及解释器状态的 C API 调用都遵循"取锁—调用—释放"的模式,这为后文并发章节埋下伏笔。

Python 回调仓颉函数:PyCFunc

py_interop库支持简单的函数注册及 Python 对仓颉函数的调用:实现一个CFunc函数并构造PyCFunc对象,即可传递给 Python 侧调用。CFunc函数入参和返回值支持的类型包括:Bool/Int8/UInt8/Int16/UInt16/Int32/UInt32/Int64/UInt64/Float32/Float64/CPointer<Unit>/CString/Unit。

实现层面,pycfunc.cj 声明了 C 侧的PyMethodDef结构(方法名、函数指针、flags、文档字符串)与回调入口py_invoke_callback,负责解析传入的回调函数标识与参数元组;测试目录tests/py_interop/cfunc/下的cfunc01.cj~cfunc04.cj覆盖了基本回调、参数类型、引用计数(refcount.cj)与协程场景(spawn.cj)。

在 Python 回调仓颉的场景下,支持在仓颉回调函数中直接抛出PythonException,仓颉异常堆栈会与 Python 调用堆栈一同抛出(用户指南给出了完整的联合堆栈示例),便于跨语言定位问题。

并发模型:GIL 与协程绑定

用户指南对并发能力有明确的边界说明:

  • 程序运行时全局仅加载一个 Python 解释器,通过全局解释器锁(GIL)确保对互操作接口的并发访问安全;
  • 接口支持系统线程间的并发场景,不支持仓颉协程间的并发场景;
  • 创建仓颉协程时,需使用CJ_BindOSThread将协程绑定到系统线程,否则调用互操作接口时可能出现 GIL 锁相关异常;
  • 主线程在调用load初始化解释器时已完成自动绑定,无需额外操作。
import py_interop.* foreign func CJ_BindOSThread(): Int32 foreign func CJ_UnbindOSThread(): Int32 main() { Python.load() spawn { unsafe {CJ_BindOSThread()} var sys = Python.importMod("sys") sys["version"] unsafe {CJ_UnbindOSThread()} } Python.unload() 0 }

两个已知限制值得注意:未绑定系统线程的spawn在调用互操作接口期间若发生系统线程切换会抛出仓颉异常;同时拉起超过 8 个绑定了系统线程的spawn时可能出现死锁(文档注明此为仓颉运行时已知问题,仍在修复中)。从源码看,python.cj 顶部声明的foreign func CJ_BindOSThread()、CJ_UnbindOSThread()、gettid()正是该并发机制的底层接口;相关测试见tests/py_interop/global/spawn01.cj~spawn05.cj与tests/py_interop/cfunc/spawn.cj。

异常处理:PythonException

PythonException继承自仓颉Exception,仅在异常前缀上与父类存在差异。使用规则:

  • 出现异常时可通过try-catch捕获;
  • 不捕获时会打印异常堆栈并退出程序,返回值为 1。

典型场景:load传入了目录路径("/usr/lib/")会抛出PythonException: "/usr/lib/" cannot be loaded.;importMod找不到模块、eval表达式语法错误(SyntaxError)、PyObj成员不存在等场景均通过该异常体系向仓颉侧报告。实现见 python.cj 中的public class PythonException <: Exception,测试见 tests/py_interop/global/python_exception.cj。

项目组织、开源协议与贡献

  • 开源协议:本项目基于 Apache-2.0 with Runtime Library Exception(见 LICENSE)与 Attribution 4.0 International(见 docs/LICENSE)双协议开源;
  • 相关仓:测试依赖 cangjie_test_framework 测试框架(README 中给出的关联项目),测试脚本 tests/test.sh 的注释说明需先下载该框架并配置依赖;
  • 参与贡献:项目欢迎提交 Issue 及任何形式的贡献。

小结

cangjie_python_interop以py_interop动态库为交付形态,围绕"解释器生命周期管理(load/isLoad/unload)→ Python 对象模型(PyObj家族与仓颉类型双向映射)→ 代码执行(eval/exec/importMod)→ 反向回调(PyCFunc)"这条主线,为仓颉程序提供了一套类型安全、异常可控的 Python 互操作方案。仓库中约 3900 行仓颉源码与 12 个功能维度的测试用例目录(tests/py_interop/)为上述每一项规格说明提供了可直接验证的依据。对于需要在仓颉应用中接入 Python 计算与 AI 生态的开发者,建议按 用户指南 的规格逐项演练,并对照 开发者指南 完成build.py build/install/clean的完整构建闭环。

  • 开发工具

【免费下载链接】cangjie_python_interop

项目地址:https://gitcode.com/Cangjie/cangjie_python_interop
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询