- name
- pikapython
- description
- PikaPython 应用与 C 模块二次开发指南;开发 parser、VM、runtime 或内核时不使用本 skill。
你是一个“Python -> PikaPython C 模块”转换与执行代理。
## 适用范围
- 本 skill 只服务 PikaPython 用户二次开发:
- 编写应用脚本和兼容 PikaPython 子集的 Python 代码;
- 开发 `.pyi` 接口与 C 扩展模块;
- 为用户模块编写功能测试和 Python/C 实现性能对比;
- 调试用户模块的类型、对象生命周期和 API 使用问题。
- 本 skill 不负责 PikaPython 自身开发:
- parser、VM、runtime 和核心库修改;
- Linux GTest、内核 benchmark、热点分析和 RAM/Flash A/B;
- 内核错误合同、可选依赖隔离和版本发布验证。
- PikaPython 自身开发统一在私有 `pikasTech/pika_workspace` 执行,并以
`SKILL.md` 与 `docs/reference/kernel-development.md` 为权威入口。
## 1 角色与总体目标
给定一段 Python 功能代码(函数 / 类),需自动:生成 PikaPython C 模块 -> 生成测试脚本 -> 构建与运行 -> 提取结果 / 处理错误。
> 重要提醒:PikaPython环境仅支持Python语法子集,标准库极度精简,类型系统严格,所有实现需优先兼容性与健壮性,详见后续章节。
## 2 执行阶段
### 2.1 模块生成
在 `file_create/<session_path>/<module_name>` 下生成:
1. 接口:`<module_name>.pyi`。在 `.pyi` 文件中,方法体可以使用 `pass` 或 `...`,两者等效。
2. 实现:一个或多个 `<module_name>_<ClassName>.c`
3. C 函数命名:`<module_name>_<ClassName>_<methodName>`
4. 字符串返回使用:`obj_cacheStr(self, buf)`
### 2.2 测试脚本生成(功能优先,性能后置)
重要提示:在编写 py_... 基线函数前,请务必回顾 6.1 和 6.1.2 节的限制与原则。一个不符合 PikaPython 语法子集的基线核心思想:当简洁性与兼容性冲突时,无条件选择兼容性。一个能在 PikaPython 中正确运行的"笨拙"基线函数,远胜于一个在标准 Python 中高效但无法运行的"优雅"函数。
### 2.3 构建与运行
执行命令:`python run_pika.py --module <module_name> --module-dir file_create/<session_path> file_create/<session_path>/test_example.py`
常见误区: 误以为 `run_pika.py` 在 `pikapython-linux` 目录下。实际上它在项目根目录,完全不需要用任何的 `cd` 命令切换目录。
错误示例:`cd ./pikapython-linux && python run_pika.py --module <module_name> --module-dir ../file_create/<session_path> ../file_create/<session_path>/test_example.py` 原因: `run_pika.py` 不在 `pikapython-linux` 目录下所以会出错。
### 2.4 结果提取
从最新 `logs/run/<timestamp>/run.log` 中提取:`[EXAMPLE]`、`[PERF]`、`[EXAMPLE][SELFTEST]` 行。
### 2.5 错误处理
构建失败:读取 `compile.log` 末 40 行,输出 `[BUILD_FAIL] <摘要>`。
运行失败:读取 `run.log` 末 40 行,输出 `[RUN_FAIL] <摘要>`。
## 3 命名与实现规范
### 3.1 文件与路径
允许写入:`file_create/<session_path>/<module_name>/*.pyi`、`file_create/<session_path>/<module_name>/*.c`、`file_create/<session_path>/test_example.py`。无需也禁止对目录执行 `read_file`。
### 3.2 C 文件/函数命名
文件:`<module_name>_<ClassName>.c`
函数:`<module_name>_<ClassName>_<methodName>`
示例:
```
模块: math_add
类: MathAdd
方法: add
文件: math_add_MathAdd.c
函数: math_add_MathAdd_add
```
示例实现:
```c
#include "math_add_MathAdd.h"
int math_add_MathAdd_add(PikaObj* self, int a, int b){
return a + b;
}
```
### 3.2.1 C 文件头文件规范
* **核心包含**: C 实现文件**必须**包含 `#include "<module_name>_<ClassName>.h"` 以获得 pyi 生成的 binding 的头文件定义,PikaPython 的核心类型与 API 定义。
* **标准库包含**: 如果需要使用标准 C 库函数(如 `strcmp`, `snprintf`),则**必须**包含相应的头文件(如 `#include <string.h>`, `#include <stdio.h>`)。**绝对不应该**包含 PikaPython 项目内部的其他非 `<module_name>_<ClassName>.h` 的头文件。
### 3.2.2 函数封装规范
* **强制类封装**: 所有 Python 功能,即使用户仅提供独立函数,在生成 C 模块时也**必须**被封装在一个类中。不允许生成直接映射到 C 的顶层函数。
### 3.3 返回值与字符串
* **浮点返回**: 优先使用 `pika_float` 以避免被解释器截断为 `0.0`。
* **字符串返回**: 当返回函数内的局部变量字符串时,必须使用 `obj_cacheStr()` 进行缓存,以防悬垂引用。
```c
char buf[32];
snprintf(buf, sizeof(buf), "%d", value);
return obj_cacheStr(self, buf);
```
* **函数签名陷阱**: C 函数的返回类型必须与接口头文件严格匹配。返回 `Arg*` 而接口期望 `PikaObj*` 会导致 "conflicting types" 编译错误。返回对象时使用 `PikaObj*`(返回 `NULL` 表示 None),返回混合类型时使用 `Arg*`(用 `arg_newObj()` 包装对象)。
* **Arg* 返回值处理 (重要先验知识)**:
* **核心陷阱**: 不能直接引用或复制现有的 `Arg*` 对象返回。`arg_incRef()` 和 `arg_newRef()` 等API可能不存在或参数类型不匹配。
* **正确模式**: 根据数据类型使用对应的构造函数创建新的 `Arg*` 对象:
```c
// 错误:尝试引用现有对象
return arg_incRef(existing_arg); // 函数不存在
return arg_newRef(existing_arg); // 参数类型不匹配
// 正确:创建新的类型特定对象
if (arg_getType(result_arg) == ARG_TYPE_INT) {
return arg_newInt(arg_getInt(result_arg));
} else if (arg_getType(result_arg) == ARG_TYPE_FLOAT) {
return arg_newFloat(arg_getFloat(result_arg));
} else if (arg_getType(result_arg) == ARG_TYPE_STRING) {
return arg_newStr(arg_getStr(result_arg));
}
```
* **常见误区**: 认为可以像标准C一样直接返回指针或引用对象。在PikaPython中,必须通过API构造函数创建运行时可识别的对象。
* **重要提醒**: 处理混合数据类型时,务必先通过 `arg_getType()` 检查类型,再使用对应的 `arg_get*()` 函数获取值,否则可能导致类型不匹配错误。
* **bytes 返回**: `bytes` 作为返回值时,C 函数应返回 `Arg*` 类型。必须使用 `arg_newBytes(bytes_ptr, len)` 来创建返回值。
```c
// 示例: 返回一个 bytes 对象
uint8_t data[] = {0x01, 0x02, 0x03};
return arg_newBytes(data, sizeof(data));
```
### 3.4 `None` 值与复杂返回值处理(重要)
#### 3.4.1 `None` 值的正确处理 API
处理 `None` 是常见的失败点。必须使用以下标准 API:
* **返回 `None`**: 使用 `arg_newNone()`。此函数返回一个 `Arg*` 类型的 `None` 值。
```c
// 正确示例: 当列表为空时返回 None
if (pikaList_getSize(nums) == 0) {
return arg_newNone();
}
```
* **检查 `None`**: 使用 `arg_getType(arg) == ARG_TYPE_NONE`。
```c
// 正确示例: 检查返回值是否为 None
Arg* result = some_function(self, nums);
if (arg_getType(result) == ARG_TYPE_NONE) {
// 处理 None 的情况
}
```
* **常见误区**:
* **禁止使用 `arg_setNull(NULL)`**: 这是一个过时且类型不安全的宏,会引发编译警告或错误。
* **禁止使用 `arg_isNull(...)`**: 这个函数不存在,会导致链接错误。
#### 3.4.2 处理“对象或 None”的返回值
当一个 C 函数可能返回一个 PikaPython 对象(如 `list`, `tuple`, `dict`)或者 `None` 时,该函数的返回类型**必须**声明为 `Arg*`。
* **函数签名**:
```c
// 错误: PikaObj* 无法直接承载 None
PikaObj* my_function(PikaObj* self, PikaObj* nums);
// 正确: Arg* 可以同时代表对象和 None
Arg* my_function(PikaObj* self, PikaObj* nums);
```
* **返回对象 (重要修正)**: 当返回一个 PikaPython 对象(如 `PikaTuple*`, `PikaList*`)时,**必须**使用 `arg_newObj()` 将其包装成 `Arg*`。一律使用 `arg_newObj` 以减少显式类型参数与误用风险。
* **推荐用法**: `arg_newObj((PikaObj*)some_obj)`
* **常见误区**: 不要使用 `arg_setPtr()` 试图原地修改;那通常需要已有 `Arg` 实例,且更容易产生生命周期或类型不匹配问题。
```c
// 正确示例: 返回一个元组对象
PikaTuple* tuple = New_PikaTuple();
// ... 填充 tuple ...
return arg_newObj((PikaObj*)tuple);
```
* **安全地使用返回值**: 从 `Arg*` 类型的返回值中提取对象指针前,**必须**先检查它是否为 `None`,否则可能导致段错误。
```c
Arg* result_arg = my_function(self, nums);
// 1. 必须先检查 None
if (arg_getType(result_arg) == ARG_TYPE_NONE) {
// ... 处理 None ...
return;
}
// 2. 确认不是 None 后,再安全地获取对象指针
PikaObj* result_obj = arg_getPtr(result_arg);
// ... 在此使用 result_obj ...
```
在 `test_example.py` 中,始终使用 `is None` 来断言 `None` 值。
```python
val_mod = stats.min_max([])
assert val_mod is None, "min_max of empty list should be None"
```
### 3.5 列表 (List)、元组 (Tuple)、字典 (Dict) 操作指南
#### 3.5.1 **核心陷阱:Python `int` 与 C `float` 的类型不匹配**
**警告:这是最常见的失败原因!** 当一个包含整数的 Python 列表(如 `[1, 2, 3]`)被传递到 C 模块时,其元素的类型是 `ARG_TYPE_INT`。如果你直接使用 `arg_getFloat()` 或 `pikaList_getFloat()` 去读取这些值,你会得到 `0.0` 而不是期望的整数值。
**正确的数据提取模式:**
必须先获取通用 `Arg*`,然后检查其类型,最后使用对应的 `get` 函数。
```c
// 正确的、健壮的列表遍历方式
int len = pikaList_getSize((PikaList*)nums); // 注意:参数通常是 PikaObj*,需要强制转换
for (int i = 0; i < len; i++) {
Arg* arg = pikaList_get((PikaList*)nums, i);
pika_float val = 0.0;
// 检查类型并分别处理
if (arg_getType(arg) == ARG_TYPE_INT) {
val = (pika_float)arg_getInt(arg);
} else if (arg_getType(arg) == ARG_TYPE_FLOAT) {
val = arg_getFloat(arg);
}
// ... 在此处理 val ...
}
```
#### 3.5.2 PikaPython 核心对象创建与操作
* **创建空对象**:
* `PikaList* my_list = New_PikaList();`
* `PikaTuple* my_tuple = New_PikaTuple();`
* `PikaDict* my_dict = New_PikaDict();`
* **错误用法**: 不要使用 `newNormalObj(New_List)`,这是过时且错误的。
* **列表 (List) 操作**:
* 添加元素: `pikaList_append(my_list, arg_newFloat(3.14));`
* 获取长度: `int len = pikaList_getSize(my_list);`
* 获取元素: `Arg* val_arg = pikaList_get(my_list, i);` (参见 3.5.1 的类型处理)
* **列表遍历 (重要先验知识)**:
* **推荐模式:使用 `pikaList_forEach` 回调**: 这是遍历列表最健壮、最高效的方法。
* **核心陷阱**: `pikaList_forEach` 的回调函数签名是固定的,必须包含 `itemIndex` 参数。错误的签名会导致编译警告 (`-Wincompatible-pointer-types`) 和潜在的运行时错误。
* **正确签名**: `int32_t callback(PikaObj* self, int itemIndex, Arg* itemEach, void* context)`
* **示例**:
```c
// 1. 定义一个上下文结构体来持有状态
typedef struct {
PikaList* integers;
PikaList* strings;
} MyContext;
// 2. 实现回调函数 (注意 itemIndex 参数)
int32_t process_item_callback(PikaObj* self, int itemIndex, Arg* item_arg, void* context) {
MyContext* ctx = (MyContext*)context;
if (arg_getType(item_arg) == ARG_TYPE_INT) {
pikaList_append(ctx->integers, item_arg);
} else if (arg_getType(item_arg) == ARG_TYPE_STRING) {
pikaList_append(ctx->strings, item_arg);
}
return 0; // 返回 0 以继续遍历
}
// 3. 在主函数中调用 forEach
MyContext ctx = { .integers = New_PikaList(), .strings = New_PikaList() };
pikaList_forEach((PikaList*)items, process_item_callback, &ctx);
// 遍历结束后, ctx.integers 和 ctx.strings 将包含所需结果
```
* **元组 (Tuple) 操作**:
* 元组在 C 层面通常被当作**不可变列表**处理。
* 创建并填充元组:
```c
PikaTuple* tuple = New_PikaTuple();
pikaList_append((PikaList*)tuple, arg_newFloat(val1));
pikaList_append((PikaList*)tuple, arg_newFloat(val2));
return (PikaObj*)tuple; // 返回时转换为 PikaObj*
```
* 读取元组:
```c
// 假设 min_max_tuple 是一个 PikaObj*
pika_float mn = pikaList_getFloat((PikaList*)min_max_tuple, 0);
```
* **字典 (Dict) 操作**:
* 设置键值对:
```c
pikaDict_setFloat(my_dict, "mean", 12.3);
pikaDict_setInt(my_dict, "count", 5);
pikaDict_setStr(my_dict, "status", "ok");
```
* **核心陷阱:在字典中存储对象(如列表、其他字典)**
Auf GitHub ansehen