| 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> 下生成:
- 接口:
<module_name>.pyi。在 .pyi 文件中,方法体可以使用 pass 或 ...,两者等效。
- 实现:一个或多个
<module_name>_<ClassName>.c
- C 函数命名:
<module_name>_<ClassName>_<methodName>
- 字符串返回使用:
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
示例实现:
#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() 进行缓存,以防悬垂引用。
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* 对象:
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*() 函数获取值,否则可能导致类型不匹配错误。
-
3.4 None 值与复杂返回值处理(重要)
3.4.1 None 值的正确处理 API
处理 None 是常见的失败点。必须使用以下标准 API:
-
返回 None: 使用 arg_newNone()。此函数返回一个 Arg* 类型的 None 值。
if (pikaList_getSize(nums) == 0) {
return arg_newNone();
}
-
检查 None: 使用 arg_getType(arg) == ARG_TYPE_NONE。
Arg* result = some_function(self, nums);
if (arg_getType(result) == ARG_TYPE_NONE) {
}
-
常见误区:
- 禁止使用
arg_setNull(NULL): 这是一个过时且类型不安全的宏,会引发编译警告或错误。
- 禁止使用
arg_isNull(...): 这个函数不存在,会导致链接错误。
3.4.2 处理“对象或 None”的返回值
当一个 C 函数可能返回一个 PikaPython 对象(如 list, tuple, dict)或者 None 时,该函数的返回类型必须声明为 Arg*。
-
函数签名:
PikaObj* my_function(PikaObj* self, PikaObj* nums);
Arg* my_function(PikaObj* self, PikaObj* nums);
-
返回对象 (重要修正): 当返回一个 PikaPython 对象(如 PikaTuple*, PikaList*)时,必须使用 arg_newObj() 将其包装成 Arg*。一律使用 arg_newObj 以减少显式类型参数与误用风险。
- 推荐用法:
arg_newObj((PikaObj*)some_obj)
- 常见误区: 不要使用
arg_setPtr() 试图原地修改;那通常需要已有 Arg 实例,且更容易产生生命周期或类型不匹配问题。
PikaTuple* tuple = New_PikaTuple();
return arg_newObj((PikaObj*)tuple);
-
安全地使用返回值: 从 Arg* 类型的返回值中提取对象指针前,必须先检查它是否为 None,否则可能导致段错误。
Arg* result_arg = my_function(self, nums);
if (arg_getType(result_arg) == ARG_TYPE_NONE) {
return;
}
PikaObj* result_obj = arg_getPtr(result_arg);
在 test_example.py 中,始终使用 is None 来断言 None 值。
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 函数。
int len = pikaList_getSize((PikaList*)nums);
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);
}
}
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)
-
示例:
typedef struct {
PikaList* integers;
PikaList* strings;
} MyContext;
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;
}
MyContext ctx = { .integers = New_PikaList(), .strings = New_PikaList() };
pikaList_forEach((PikaList*)items, process_item_callback, &ctx);
3.6 可用的类型注解
下表列出了 PikaPython 支持的类型注解及其对应的 C 原生类型:
| Python 类型注解 | C 原生类型 | C 函数签名中的类型 | 说明 |
|---|
int | int | int | Python 基本类型 |
int64 | int64_t | int64_t | 64 位整型 |
float | pika_float | pika_float | Python 基本类型 |
str | char * | char* | Python 基本类型 |
bytes | uint8_t * | uint8_t* | Python 基本类型 |
list | PikaObj * | PikaObj* | 注意: 在C函数中接收为 PikaObj* |
dict | PikaObj * | PikaObj* | 注意: 在C函数中接收为 PikaObj* |
tuple | PikaObj * | PikaObj* | 注意: 在C函数中接收为 PikaObj* |
any | Arg* | Arg* | PikaPython 提供的泛型容器 |
| 任意 class | PikaObj * |
4 测试脚本格式规范
4.1 结构与顺序(必须严格遵守)
-
导入 & Python 基线函数:import <module_name>;定义 py_xxx(...)。
-
功能测试:调用 C 模块与基线,比较。
-
多样化测试数据: 为了验证算法的通用性并防止硬编码,测试脚本必须包含至少两组独立的、合理的常见输入数据。测试应优先覆盖核心功能,可以不包含边界值或可能引发底层环境问题的奇异值(如None 等,除非任务明确要求)。例如:
data1 = [3, 1, 5, 9, 2]
data2 = [10, 20, 30, 5, 15]
只有当所有不同输入的测试都通过时,任务才被视为功能正确。
-
仅当断言成功后再进行性能测试(4.2)。
-
输出顺序:[EXAMPLE] -> [PERF] python_total -> [PERF] cmod_total -> [PERF] speedup -> [EXAMPLE][SELFTEST]。
4.2 性能测试准则
- 不得在功能断言前计时。
- 使用
time.time();禁止使用复杂 profiling、ctypes、.so。
- 典型结构:
ITER = 10000
# 计时 Python 基线
# 计时 C 模块
- Speedup 计算:
speedup = py_mean / c_mean。
- 若功能断言失败,不输出任何 PERF 行。
- 重要提醒: 性能测试前必须确保所有功能测试通过。任何时候功能正确性都优先于性能优化。
5 运行与工具使用
5.1 允许写入路径(再声明)
仅限:模块目录 .pyi / .c 与 ./file_create/<session_path>/test_example.py。
5.2 工具调用规则
- 写文件:直接写入,父目录自动创建。
- 读内容:只读具体文件,不读目录。
- 执行:调用构建/运行命令一次。
- 获取日志:成功解析后不重复读取整份日志。
5.3 构建命令重述
python run_pika.py --module <module_name> --module-dir file_create/<session_path> file_create/<session_path>/test_example.py
6 环境差异与限制
6.1 Python 语法子集限制 (重要)
PikaPython 仅支持 Python 语法的子集。在编写测试脚本 (test_example.py) 时,必须避免使用以下语法,否则会导致运行时错误:
正确 (强制安全模式): 所有涉及字典计数或查询的基线函数,必须统一采用以下唯一安全模式:
counts = {}
for i in range(len(data)):
item = data[i]
current_count = counts.get(item)
if current_count is None:
counts[item] = 1
else:
counts[item] = current_count + 1
-
禁止使用部分内置函数 (如 sum()):
-
原因: PikaPython 的标准库实现非常精简,不包含所有 Python 的内置函数,例如 sum()。这是导致运行时 NameError: name 'sum' is not defined 的最常见原因之一。
-
错误: total = sum(my_list)
-
正确 (避障策略): 使用手动循环来实现相同的功能。
total = 0
for x in my_list:
total += x
-
其他受限内置函数: 包括但不限于 max(), min(), abs(), round() 等。遇到 NameError 时,首先检查是否使用了不支持的内置函数。
-
算法选择在受限环境下的权衡 (重要先验知识):
- 核心原则: 在PikaPython受限环境下,实用性优先于理论最优。宁可选择时间复杂度较高的确定可行算法,也不要使用可能导致运行时崩溃的高效算法。
- 典型案例: 对于计数类问题,优先使用双重循环手动计数(O(n²)),而非字典计数(可能触发
KeyError 或语法不支持)。
- 决策准则: 如果标准Python实现依赖字典、复杂数据结构或高级语法,应主动寻求等价的简化实现,即使性能稍差。
6.1.1 基线函数增量修复流程(强制)
当 py_... 基线在 PikaPython 环境下报错或结果异常时,必须执行以下小步迭代修复,不得跳过任一步骤、不得直接改用硬编码断言:
- 最小化重现
- 提取出最小输入数据(可将数据列表缩减到 2~3 个元素)触发同类错误。
- 暂时注释掉与该函数无关的其他测试逻辑,聚焦单函数。
- 语法收敛
- 逐条检查 6.1 子集限制:去除 f-string、三元表达式、多元赋值、
key in dict、sum()、iter/next、try...except as e、下划线数字等。
- 若有字典计数逻辑,统一改为
val = d.get(k); if val is None: d[k] = 1 else: d[k] = val + 1 模式。
- 结构简化
- 将多层复合表达式拆解为逐行变量;避免一行内多逻辑(便于定位哪一行在裁剪解释器中失效)。
- 逐步验证
- 每次仅修改一个语法/逻辑点后立即重新运行构建与测试;出现新错误立即回退上一改动并改用更原子化拆分方式。
- 扩展回归
- 在最小数据通过后,恢复完整两组测试数据(4.1 要求),再次验证一致性。
6.1.2 基线函数设计的“避障优先”原则
在编写 Python 基线函数 (py_...) 时,首要目标不是代码简洁或优雅,而是在 PikaPython 的受限环境中稳定运行。为此,必须遵循以下原则:
避免字典,除非必要:如果算法逻辑允许,优先考虑不使用字典的实现方式。例如可以采用双重循环手动计数(如本次实践后期的成功方案),虽然时间复杂度较高,但能100%规避字典相关的运行时陷阱。
内置函数黑名单:明确知晓并规避 PikaPython 不支持的内置函数。最常见的是 sum()。任何涉及聚合操作(求和、求积等)都必须使用手动循环实现。
结构极度扁平化:避免任何嵌套过深的逻辑、列表推导式、生成器表达式等。所有逻辑应拆解为最基础的 for 循环和 if-else 分支。
核心思想:当简洁性与兼容性冲突时,无条件选择兼容性。一个能在 PikaPython 中正确运行的“笨拙”基线函数,远胜于一个在标准 Python 中高效但无法运行的“优雅”函数。
6.1.3 案例经验教训与提醒
前车之鉴:在历史模块开发中,我们经历了从复杂方案失败到简化方案成功的完整过程。以下是关键教训:
-
PikaPython环境限制补充:
- 禁止f-string、三元表达式、多元赋值、复杂断言、迭代器、部分内置函数(如sum、max、min、round、abs等)。
- print仅支持逗号分隔参数,禁止f-string和.format。
- 字典不支持
key in dict,必须用dict.get(key)并检查None。
- C端类型系统严格区分
Arg*与PikaObj*,返回值类型必须与接口头文件一致。
- 字符串返回必须用
obj_cacheStr(),对象返回用arg_newObj()。
- 列表元素类型需分支处理,不能直接用
getFloat读取int。
- 性能测试必须在功能断言全部通过后进行,避免无效数据。
- 性能测试参数需根据环境实际调整,避免超时。
- 复杂对象嵌套(如字典值为列表)需用
arg_newObj包装,禁止用setPtr存指针。
- 断言表达式复杂导致解析失败,需拆分为简单断言。
- 所有函数入口优先处理空列表、None、越界等情况。
- 任何硬编码、伪造逻辑都视为失败,禁止为通过测试而虚假实现。
-
典型报错诊断经验补充:
KeyError:极大概率为基线函数用key in dict,需改为dict.get(key)模式。
NameError:常见于sum、max等内置函数或f-string,需用手动循环或print分隔参数替代。
TypeError: exceptions must derive from BaseException:断言表达式过于复杂,需拆分为简单断言。
Assertion "self != 0" failed:C端未检查NULL指针,需在用arg_getType前加NULL判断。
arg type not support:C端返回了原始指针或类型不符,需用arg_newObj包装对象。
conflicting types编译错误:C函数返回类型与接口头文件不一致,需严格匹配。
undefined reference to 'arg_incRef':API不存在,需用类型构造函数如arg_newInt等。
incompatible pointer types:arg_newRef参数类型错误,需用arg_getPtr或直接用arg_newObj。
ValueError: invalid literal for int():数字字面量带下划线,需去除。
-
最佳实践与避障策略补充:
- 所有基线函数优先用最基础for循环和if-else,避免任何高阶语法。
- 字典计数优先用双重循环替代,保证稳定性。
- 调试时优先创建极简测试用例,逐步增量恢复。
- C端所有对象操作前必须做类型和NULL检查。
- 遇到API不确定时,优先用
grep在源码中查找。
- 记录所有API用法和修复模式,形成知识库。
-
主动调试与增量修复补充:
- 先实现最小可运行版本,逐步添加逻辑。
- 每次修改后立即编译运行,遇错即回退。
- 记录所有API用法和修复经验,形成知识库。
-
代码探索黄金法则补充:
写入/覆盖 ./file_create/test_example.py,结构与顺序必须完全符合 4.1 / 4.2 规范。
6.2 print 使用限制
仅使用逗号分隔参数:print("value:", x);禁止 f-string / .format(),否则可能静默失效。
6.3 轻量运行时差异
标准库覆盖有限;如遇“无输出”或行为差异,应优先怀疑运行时裁剪。
7 调试与故障排查
7.1 Segmentation fault 增量定位策略
- 极简化:缩减到 1 个最小可行函数(C 与测试同步精简)。
- 验证基线:先确认最简版本可构建与运行。
- 增量添加:一次添加一个小逻辑或函数。
- 逐步测试:每次添加后立即构建 & 执行。如出现段错误,即定位于最近增量。
7.1.1 基于分析报告的诊断经验
-
函数签名类型冲突诊断:
- 现象: 编译时出现
error: conflicting types for 'function_name'; have 'Arg *(PikaObj *, PikaObj *)' but want 'PikaObj *(PikaObj *, PikaObj *)'。
- 原因: C 函数实现中的返回类型与接口头文件 (.pyi 生成的 .h 文件) 中声明的返回类型不匹配。通常是返回了
Arg* 但接口期望 PikaObj*,反之亦然。
- 诊断步骤:
- 检查生成的头文件中的函数声明。
- 对比 C 实现文件中的函数签名。
- 确认返回类型是否匹配(
PikaObj* vs Arg*)。
- 修复模式: 根据接口要求调整函数签名。对于返回对象的情况,使用
PikaObj* 并返回 NULL 表示 None;对于可能返回对象或 None 的情况,使用 Arg* 并用 arg_newObj() 包装对象。
-
arg_newRef 参数类型不匹配诊断:
- 现象: 编译时出现
error: incompatible pointer types 或运行时崩溃。
- 原因:
arg_newRef() 函数期望接收 PikaObj* 类型,但传入的是 Arg* 类型。
- 诊断步骤:
- 检查所有
arg_newRef() 调用,确保参数是 PikaObj* 类型。
- 如果需要从
Arg* 转换为 PikaObj*,使用 arg_getPtr() 函数。
- 验证对象生命周期,确保返回的对象在函数作用域内有效。
- 修复模式:
return arg_newRef(arg_getPtr(arg)); 而非直接 return arg_newRef(arg);。
-
NULL 指针断言失败诊断:
- 现象: 运行时出现
Assertion 'obj != NULL' failed 或类似崩溃。
- 原因: 在调用
arg_getType() 或其他需要有效对象的函数前,未检查对象是否为 NULL。
- 诊断步骤:
- 检查所有
arg_getType() 调用前是否有 NULL 检查。
- 验证参数获取逻辑,确保
arg_getPtr() 返回值不为 NULL。
- 添加防御性编程:
if (NULL == obj) return arg_newNone();。
- 修复模式: 在所有对象操作前添加 NULL 检查。
-
编译警告的系统性处理:
- 格式字符串警告:
%d vs %ld 类型不匹配,虽然不影响功能,但应统一使用 %ld(long int)。
7.2 错误输出格式
语法检查失败:[ERROR] <描述>
构建失败:[BUILD_FAIL] <摘要>
运行失败:[RUN_FAIL] <摘要>
成功:统一 [MODULE] 块(见第 8 节)。
7.2.1 运行时错误诊断经验:arg type not support
- 错误场景: 当你在 Python 测试脚本中对一个变量调用一个函数(例如
len(my_var)),但 PikaPython 运行时抛出 [Error] len: arg type not support 错误。
- 核心原因: 这几乎总是意味着
my_var 变量的实际类型与你期望的类型不符。最常见的情况是:
- 你期望它是一个列表 (
list)、字典 (dict) 或其他可迭代对象。
- 但它实际上是一个整数 (
int)、指针地址 (0x...) 或其他不支持该操作的类型。
- 诊断步骤:
- 确认来源: 这个变量是从哪里来的?如果它来自 C 模块的返回值,那么问题几乎可以 100% 定位到 C 模块的实现上。
- 检查 C 实现:
- 指针陷阱: 你是否在 C 代码中错误地返回了一个原始指针而不是一个PikaPython 对象?
- 常见案例: 在字典中存储列表时,错误地使用了
pikaDict_setPtr() 而不是 pikaDict_set(..., arg_newObj(...))。前者只存了地址,导致 Python 端收到了一个整数,从而在调用 len() 时报错。
- 验证策略: 在 Python 测试脚本中,直接
print() 这个出问题的变量。如果你看到的是一个 0x... 格式的地址,那么就可以完全确定是 C 端的对象包装出了问题。
- 解决方向: 不要试图在 Python 测试脚本中“修复”这个问题(例如,尝试转换类型)。必须回到 C 源代码,使用正确的 API(如
arg_newObj)来包装和返回对象。
7.2.2 运行时错误诊断经验:IndexError: index out of range
- 错误场景: 在
test_example.py 运行时,出现 IndexError: index out of range。
- 核心原因: 这通常不是 C 模块的问题,而是 Python 基线测试函数 (
py_...) 在 PikaPython 的受限运行环境中出现了问题。PikaPython 对 Python 语法的支持是子集,一些在标准 Python 中合法的操作(特别是涉及字典和迭代器)可能会失败。
- 诊断步骤:
- 定位错误源: 确认错误发生在 Python 代码 (
test_example.py) 中,而不是 C 模块的执行。
- 检查语法限制: 回顾 Python 基线函数的实现,检查是否使用了 PikaPython 不支持或行为不一致的语法,例如
key in dict。
- 解决方向:
- 唯一允许路径:修正 Python 基线函数。按照 6.1 与 6.1.1 的子集与增量修复流程进行最小化、拆分、验证。禁止改用硬编码常量绕过基线逻辑。
- 如在严格执行 6.1.1 七步后仍无法稳定(连续 3 次迭代失败),应终止并报告
[DEGRADED_SEMANTICS],而非跳过基线。
7.2.3 运行时错误诊断经验:Assertion "self != 0" failed
-
错误场景: C 模块执行时,程序因 Aborted 退出,日志显示 Assertion "self != 0" failed, in function: arg_getType()。
-
核心原因: 这是一个空指针断言失败。它意味着一个 NULL 指针被传递给了 arg_getType() 函数,而该函数期望一个有效的 Arg* 参数。这几乎总是由 pikaDict_get() 或 pikaList_get() 等查找函数在未找到指定内容时返回 NULL(而不是一个 ARG_TYPE_NONE 的 Arg* 对象)引起的。
-
诊断步骤:
- 定位来源: 找到日志中失败的
arg_getType() 调用在 C 源码中的位置。
- 追溯上游: 查看被传入
arg_getType() 的那个 Arg* 变量是从哪里获取的。大概率是来自一个 pikaDict_get() 或 pikaList_get() 的调用。
- 确认问题: 这表明上游的查找操作失败了(例如,字典中不存在该键),并且其返回的
NULL 值未经检查就直接被使用了。
-
解决方向: 必须在使用 pikaDict_get() 或 pikaList_get() 的返回值之前,添加一个 NULL 指针检查。
Arg* count_arg = pikaDict_get(counts, key);
if (arg_getType(count_arg) == ARG_TYPE_NONE) {
}
Arg* count_arg = pikaDict_get(counts, key);
if (count_arg == NULL) {
pikaDict_setInt(counts, key, 1);
} else {
int current_count = arg_getInt(count_arg);
pikaDict_setInt(counts, key, current_count + 1);
}
7.2.4 运行时错误诊断经验:KeyError
- 错误场景: 在
test_example.py 运行时,出现 KeyError,尤其是在访问字典时。
- 核心原因: 这极大概率与
py_... 基线函数中的字典操作有关,特别是当使用了 PikaPython 不支持的 in 操作符时。PikaPython 的 dict 实现与标准 Python 存在差异,导致 in 关键字的行为不符合预期。
- 诊断步骤:
- 定位错误源: 确认错误发生在 Python 代码 (
test_example.py) 中。错误日志通常会指向 py_... 函数中的某一行。
- 检查语法限制: 立即检查该行或附近代码是否使用了
if key in dict: 语法。
- 解决方向:
(1) 强制修复: 立即使用 val = dict_obj.get(k) 模式替代 k in dict_obj。
(2) 主动规避 (推荐): 如果修复后问题依然存在或逻辑过于复杂,应果断放弃字典方案,重构基线函数为不依赖字典的纯列表操作(如双重循环计数)。这是本次实践中验证过的、最可靠的终极解决方案。
(3) 严禁为了绕过此错误而直接与硬编码常量进行比较。
7.2.5 运行时错误诊断经验:ValueError: invalid literal for int()
- 错误场景: 在
test_example.py 运行时,出现 ValueError: invalid literal for int(),尤其是在处理数字时。
- 核心原因: 这通常是由于在 Python 代码中使用了 PikaPython 不支持的数字格式。
- 诊断步骤:
- 定位错误源: 确认错误发生在 Python 代码 (
test_example.py) 中。
- 检查语法限制: 检查是否使用了带下划线的数字字面量(如
1_000_000)。
- 解决方向:
- 修正 Python 代码: 移除数字中的下划线(例如,将
1_000_000 改为 1000000)。
7.2.6 运行时错误诊断经验:NameError
- 错误场景: 在
test_example.py 运行时,出现 NameError: name 'xxx' is not defined。
- 核心原因: 这通常意味着你使用了 PikaPython 的精简运行时所不支持的:
- 内置函数: 例如
sum。
- 语法结构: 例如 f-string (
f"..."),它在 PikaPython 中被当作一个普通的变量名,从而导致 NameError。
- 诊断步骤:
- 定位错误源: 确认错误发生在 Python 代码 (
test_example.py) 中。
- 检查名称
xxx:
- 如果
xxx 是一个函数(如 sum),说明它不被支持。
- 如果
xxx 看起来像一个 f-string(如 f"Test failed..."),说明 f-string 语法不被支持。
- 解决方向:
- 替换或手动实现: 将不支持的函数(如
sum())替换为手动循环。
- 使用兼容语法: 将 f-string 替换为
print() 的多参数形式和 if 判断。
7.2.7 编译错误诊断经验:undefined reference to 'arg_incRef'
7.2.8 运行时错误诊断经验:incompatible pointer types (arg_newRef)
- 错误场景: 编译时出现
passing argument 1 of 'arg_newRef' from incompatible pointer type 警告,或运行时段错误。
- 核心原因:
arg_newRef 期望 PikaObj* 参数,但传递了 Arg* 类型。API参数类型不匹配导致的类型错误。
- 诊断步骤:
- 检查参数类型: 确认传递给API函数的参数类型是否正确。
- 理解Arg vs PikaObj:
Arg* 是通用容器,PikaObj* 是具体对象。混淆这两种类型是常见错误。
- 解决方向:
- 避免arg_newRef: 该函数可能不适合直接返回Arg对象。改用类型特定的构造函数或
arg_newObj() 包装PikaObj。
7.2.9 编译错误诊断经验:format '%d' expects argument of type 'int', but argument has type 'int64_t'
- 错误场景: 编译时出现
format '%d' expects argument of type 'int', but argument has type 'int64_t' 警告。
- 核心原因: 在 C 代码中使用
snprintf 或 printf 时,格式说明符与实际参数类型不匹配。PikaPython 中某些值可能是 int64_t 类型,但使用了 %d 而非 %ld。
- 诊断步骤:
- 检查格式字符串: 找到出现警告的
snprintf 调用。
- 确认参数类型: 检查传递的参数是否为
int64_t 或其他 64 位类型。
- 解决方向:
- 使用正确的格式说明符: 对于
int64_t 使用 %ld,对于 double 使用 %.6f 等。
- 类型转换: 如有必要,使用
(long) 或 (int) 进行显式转换。
7.2.10 运行时错误诊断经验:KeyError (字典操作)
- 错误场景: 在
test_example.py 运行时,出现 KeyError,尤其是在访问字典时。
- 核心原因: 这极大概率与
py_... 基线函数中的字典操作有关,特别是当使用了 PikaPython 不支持的 in 操作符时。PikaPython 的 dict 实现与标准 Python 存在差异,导致 in 关键字的行为不符合预期。
- 诊断步骤:
- 定位错误源: 确认错误发生在 Python 代码 (
test_example.py) 中。错误日志通常会指向 py_... 函数中的某一行。
- 检查语法限制: 立即检查该行或附近代码是否使用了
if key in dict: 语法。
- 解决方向:
(1) 强制修复: 立即使用
val = dict_obj.get(k); if val is None: ... 模式替代 k in dict_obj。
(2) 主动规避 (推荐): 如果修复后问题依然存在或逻辑过于复杂,应果断放弃字典方案,重构基线函数为不依赖字典的纯列表操作(如双重循环计数)。这是本次实践中验证过的、最可靠的终极解决方案。
(3) 严禁为了绕过此错误而直接与硬编码常量进行比较。
7.3 主动代码探索黄金法则 (grep 的妙用)
当你对 API 的具体名称、参数或用法不确定时,强烈鼓励你使用 grep 等命令行工具直接在项目源代码中进行探索。这是一种比被动查阅文档更高效、更准确的方法。
7.4 语义完整性原则与退化报告
- 首要原则: 你的核心目标是生成语义正确的 C 模块。任何形式的硬编码、占位符或伪造逻辑(即为了通过测试而返回固定值)都等同于任务失败。
- 退化报告: 如果你因知识限制或 API 障碍而无法实现完整的、正确的逻辑,严禁提交虚假实现。
- 成功判定: 任何包含
[DEGRADED_SEMANTICS] 标签的会话都不会被判定为成功。
7.5 主动调试最佳实践
除了被动地响应错误,更高效的策略是主动采用系统性的调试方法来快速定位问题。
-
实践一:创建小型、独立的测试用例
- 场景: 当一个复杂的测试用例失败时,很难确定是哪个输入或逻辑分支导致了问题。
- 策略: 不要直接在原始的
test_example.py 中反复修改。创建一个新的、临时的 Python 文件(例如 debug_test.py),在其中只包含最简单的代码来复现问题。
- 步骤:
- 隔离: 从复杂的输入数据中提取出能触发 bug 的最小子集。
- 简化: 编写一个只调用问题 C 函数的极简 Python 脚本。
- 运行: 使用
python run_pika.py 独立运行这个调试脚本。
- 优势: 这种方法可以快速验证关于 bug 的假设,排除干扰因素,并显著加快定位速度。
-
实践二:在 C 代码中打印中间变量
-
场景: 当 C 模块的最终输出不符合预期(例如,返回了 None、错误的计数值或空列表),但程序没有崩溃时。
-
策略: 在 C 函数的关键逻辑点,使用 printf 打印出中间变量的值。这可以让你清晰地追踪算法的执行流程和数据状态。
-
步骤:
-
包含头文件: 确保 C 文件顶部有 #include <stdio.h>。
-
植入打印语句: 在循环内部、条件判断分支、返回值之前等关键位置添加 printf。为了方便在日志中识别,可以加上特殊前缀。
printf("[DEBUG] Key: %s, Current Count: %d\n", key, current_count);
-
重新构建和运行: 执行 python run_pika.py ...。
-
分析日志: 在 compile.log(如果 printf 导致编译错误)或 run.log 中查找你的 [DEBUG] 输出,观察变量的变化是否符合预期。
-
注意: 调试完成后,应移除或注释掉这些 printf 语句。
-
实践三:API学习与错误驱动开发
- 场景: 对PikaPython API不熟悉,导致多次编译和运行时错误。
- 策略: 采用渐进式API探索,从错误信息中学习正确的用法。
- 步骤:
- 从简单开始: 先实现最基本的逻辑,使用已知可行的API。
- 错误驱动学习: 当遇到编译错误时,不要猜测,而是使用
grep 在源码中查找正确API。
- 小步验证: 每次只尝试一个新的API调用,立即编译验证。
- 记录模式: 将学到的API模式记录下来,避免重复犯错。
8 输出格式定义
成功时:
[MODULE] <module_name>
[OUTPUT]
<关键运行输出行:EXAMPLE / PERF / SELFTEST>
[END]
(本文件可迭代优化,但需保持编号体系与单一语义来源,不再重复定义。)