KBEngine 嵌入式 Python 整数对象 C API(PyLong)权威指南 游戏开发后端【免费下载链接】kbengineA MMOG engine of server.项目地址https://gitcode.com/gh_mirrors/kb/kbengine点击查看免费下载本指南以 KBEngine 仓库内嵌的 CPython 3.7.3 官方文档 kbe/src/lib/python/Doc/c-api/long.rst 为骨架逐条讲解 Python C API 中整数对象PyLong的类型结构、创建From 系列与提取As 系列全部接口并结合仓库源码longobject.c、longobject.h以及 KBEngine 在实体属性类型、内存流读写中的真实调用说明如何在游戏服务端 C 与 Python 脚本之间安全地传递任意精度整数。读完本文你将掌握如何判断一个PyObject*是否为整数、如何把 C 的long/long long/unsigned long long/Py_ssize_t/指针等转换成 Pythonint如何把 Pythonint无损地取回 C 类型并正确处理溢出与错误以及 KBEngine 的UINT64/INT64等实体属性在Python int ↔ C 类型 ↔ 网络/数据库流之间转换的底层实现方式。一、背景KBEngine 中为何关心 PyLongKBEngine 是一款以 Python 为脚本语言的多人在线游戏服务端引擎其 C 服务端进程baseapp、cellapp、dbmgr 等内嵌了完整的 CPython 解释器当前仓库为 patchlevel.h 标识的 Python 3.7.3。这意味着开发者用 Python 编写的实体属性如UINT32、INT64最终都以 Pythonint对象的形式存在于解释器中当这些属性需要写入网络流baseapp 与 cellapp 通信、数据库MySQL/Redis或与 C 数值运算时就必须借助 PyLong 系列 API 在 C 类型与 Python 对象之间转换服务端存在大量数字密集型逻辑ID 分配、坐标、时间戳、统计计数PyLong 的使用频率极高。因此PyLong C API 是连接 KBEngine 引擎层C与玩法层Python的核心桥梁之一。二、整数对象模型PyLongObject 与 PyLong_Type2.1 任意精度整数在 CPython 3.x 中所有整数都实现为任意精度的 long 整数对象Python 层的int在 C 层就是PyLongObjectPyLongObjectPyObject的子类型代表一个 Python 整数对象。它由符号 若干 30 位或 15 位视平台宏PyLong_SHIFT而定数字位组成因此可以表达远超 C 内置整数范围的值。PyLong_TypePyTypeObject类型的实例即 Python 层int类型对应的 C 类型对象。在仓库头文件 longobject.h 中可以看到其声明typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */ PyAPI_DATA(PyTypeObject) PyLong_Type;2.2 小整数缓存-5 ~ 256官方文档特别提示了一个经典实现细节PyLong_FromLong当前实现会为-5到256之间所有整数维护一个对象数组当创建该范围内的整数时实际返回的是既有对象的引用引用计数 1而不是新建对象。因此从技术上说可以通过某种方式改变1这个对象的值——但 Python 在该场景下的行为是未定义的切勿尝试。这一机制在仓库源码 longobject.c 中由两个宏定义#define NSMALLPOSINTS 257 /* 0 .. 256 */ #define NSMALLNEGINTS 5 /* -1 .. -5 */配合预分配的静态数组small_ints[NSMALLNEGINTS NSMALLPOSINTS]第 43 行与get_small_int()第 48-62 行实现。PyLong_FromLong在进入分配逻辑前先执行CHECK_SMALL_INT(ival)第 251 行快速返回缓存对象。这一设计让高频出现的小整数如循环计数、状态码、布尔值零分配复用显著减少内存与 GC 压力——对 KBEngine 这类每帧处理海量实体的服务端尤为重要。三、类型检查PyLong_Check 与 PyLong_CheckExactint PyLong_Check(PyObject *p)当参数是PyLongObject或其子类型时返回真。int PyLong_CheckExact(PyObject *p)仅当参数精确是PyLongObject不是子类型时返回真。两者在头文件中的实现差异直观展示了包含子类与排除子类的区别#define PyLong_Check(op) PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LONG_SUBCLASS) #define PyLong_CheckExact(op) (Py_TYPE(op) PyLong_Type)KBEngine 应用实例在实体属性类型系统 datatype.cpp 中UInt64Type::isSameType()首先调用PyLong_Check(pyValue)确认脚本传入的默认值/属性值确实是整数对象否则抛出类型错误if (!PyLong_Check(pyValue)) { OUT_TYPE_ERROR(UINT64); return false; }随后才调用PyLong_AsUnsignedLongLong(pyValue)等转换接口并检查PyErr_Occurred()形成先检查类型、再尝试转换、最后核对异常的稳健范式。四、创建整数对象PyLong_From* 系列4.1 从有符号整数创建PyObject* PyLong_FromLong(long v); PyObject* PyLong_FromLongLong(long long v); PyObject* PyLong_FromSsize_t(Py_ssize_t v);返回新的PyLongObject失败返回NULL。PyLong_FromLong是最高频的入口先命中-5~256小整数缓存再按PyLong_SHIFT位数逐段构造对象longobject.c。PyLong_FromLongLong处理 64 位 C 整数PyLong_FromSsize_t处理平台相关的Py_ssize_t通常为 64 位适合表示长度、索引、缓冲区大小。4.2 从无符号整数创建PyObject* PyLong_FromUnsignedLong(unsigned long v); PyObject* PyLong_FromUnsignedLongLong(unsigned long long v); PyObject* PyLong_FromSize_t(size_t v);用于把 C 的无符号类型转成 Pythonint覆盖数据库自增 ID、字节长度、内存地址等非负数值场景。KBEngine 的py_memorystream.cpp在向 Python 层返回流读写位置时大量使用PyTuple_SET_ITEM(args1, 0, PyLong_FromUnsignedLong(pPyMemoryStream-stream().rpos())); PyTuple_SET_ITEM(args1, 1, PyLong_FromUnsignedLong(pPyMemoryStream-stream().wpos()));4.3 从浮点数创建PyObject* PyLong_FromDouble(double v);取v的整数部分构造PyLongObject失败返回NULL。注意它不进行四舍五入行为等价于 Python 层的int(float_value)。4.4 从字符串创建PyObject* PyLong_FromString(const char *str, char **pend, int base);根据基数base解释str中的数字串构造整数。若pend非NULL*pend会指向str中紧跟数字表示之后的第一个字符可用于字符串解析器逐段消费输入。若base 0按 Python 的 整数字面量规则 解释即支持0x/0o/0b前缀自动判定进制此时若一个非零十进制数带有前导零会抛出ValueErrorPython 3 禁止0123这种写法。若base ! 0则必须在 2~36含之间。忽略前导空格以及进制前缀之后、数字之间的单个下划线Python 3.6 的字面量下划线分隔符。若没有任何数字抛出ValueError。4.5 从 Unicode 字符串创建PyObject* PyLong_FromUnicode(Py_UNICODE *u, Py_ssize_t length, int base); PyObject* PyLong_FromUnicodeObject(PyObject *u, int base);两者均先将 Unicode 串用PyUnicode_EncodeDecimal编码为字节串再交给PyLong_FromString完成解析。PyLong_FromUnicode属于旧的Py_UNICODEAPI自 3.3 起标记弃用、计划 4.0 移除请迁移到PyLong_FromUnicodeObject3.3 新增。4.6 从指针创建PyObject* PyLong_FromVoidPtr(void *p);把 C 指针p的值即地址数值转成 Python 整数供脚本以整数形式安全持有句柄配套的取回接口是PyLong_AsVoidPtr。KBEngine 中各类资源句柄、原生对象指针需要透传给 Python 层时即可采用此通道。五、提取 C 值PyLong_As* 系列以下接口把 Pythonint对象转换回 C 类型。共同要点若obj不是PyLongObject实例会先尝试调用其__int__方法若存在转换成PyLongObject后再提取值超出目标 C 类型范围时抛出OverflowError出错时返回哨兵值如-1、-1.0、NULL必须用PyErr_Occurred()区分合法值与错误因为-1本身可能是合法数值。5.1 有符号提取函数返回类型溢出异常备注PyLong_AsLonglongOverflowError相对LONG_MAX/LONG_MIN错误时返回-1PyLong_AsLongAndOverflowlong通过*overflow报告见下文PyLong_AsLongLonglong longOverflowError3.2 起提供PyLong_AsLongLongAndOverflowlong long通过*overflow报告见下文PyLong_AsSsize_tPy_ssize_tOverflowError相对PY_SSIZE_T_MAX要求精确为PyLongObject实例*AndOverflow变体的语义以PyLong_AsLongAndOverflow(PyObject *obj, int *overflow)为例若obj的值大于LONG_MAX或小于LONG_MIN将*overflow置为1或-1并返回-1否则*overflow置0若发生其他异常如__int__失败*overflow置0并照常返回-1。PyLong_AsLongLongAndOverflow与之等价只是比较对象换成PY_LLONG_MAX/PY_LLONG_MIN。该变体的价值在于调用方既能通过*overflow精确得知是否因超范围而失败又能继续用PyErr_Occurred()排查其他错误。5.2 无符号提取函数返回类型溢出异常备注PyLong_AsUnsignedLongunsigned longOverflowError相对ULONG_MAX要求精确为PyLongObject错误返回(unsigned long)-1PyLong_AsSize_tsize_tOverflowError相对SIZE_MAX要求精确为PyLongObjectPyLong_AsUnsignedLongLongunsigned long longOverflowError自 3.1 起负值抛OverflowError而非TypeErrorPyLong_AsUnsignedLongMaskunsigned long无溢出时按ULONG_MAX 1取模掩码语义PyLong_AsUnsignedLongLongMaskunsigned long long无溢出时按PY_ULLONG_MAX 1取模掩码语义掩码Mask变体的关键差异普通 As 接口在值过大时抛OverflowError而 Mask 系列采用截断取模语义把值折叠进目标位宽类似 C 的隐式窄化不抛异常。这在需要低 N 位位运算、哈希计算、随机数生成等场景非常实用。KBEngine 应用实例UInt64Type::addToStream()采用逐级降档策略把脚本整数写入网络流——先尝试PyLong_AsUnsignedLongLong失败则清错后尝试PyLong_AsUnsignedLong再失败则尝试PyLong_AsLong每次转换后用PyErr_Occurred()判断并PyErr_Clear()清理异常最终得到uint64 v后通过(*mstream) v;写入内存流uint64 v static_castuint64(PyLong_AsUnsignedLongLong(pyValue)); if (PyErr_Occurred()) { PyErr_Clear(); v (uint64)PyLong_AsUnsignedLong(pyValue); if (PyErr_Occurred()) { PyErr_Clear(); v (uint64)PyLong_AsLong(pyValue); // ... 最终失败则报 TypeError 并置 0 } } (*mstream) v;对应地UInt64Type::createFromStream()从流读出uint64 val后用PyLong_FromUnsignedLongLong(val)重建 Python 整数供脚本使用。这种流读出 → C 类型 →PyLong_From*→ Python 对象 →PyLong_As*→ C 类型 → 流写入的往返正是 KBEngine 实体属性在进程间与数据库间传输的通用模式。5.3 浮点与指针提取double PyLong_AsDouble(PyObject *pylong)转成 Cdouble超出double范围抛OverflowError出错返回-1.0需用PyErr_Occurred()判别。void* PyLong_AsVoidPtr(PyObject *pylong)把整数转回 C 指针仅对由PyLong_FromVoidPtr产生的值保证可用的指针转换失败抛OverflowError出错返回NULL。六、错误处理范式-1 返回值的歧义官方文档反复强调的第一守则大多数PyLong_As*API 出错时返回(返回类型)-1而-1本身完全可能是合法数值例如PyLong_AsLong(PyLong_FromLong(-1))。因此绝不能只用返回值判断成败必须配合if (PyErr_Occurred()) { /* 处理异常打印、清理或转换 */ PyErr_Clear(); }KBEngine 源码正是这一范式的教科书级应用——datatype.cpp与py_memorystream.cpp中几乎每个PyLong_As*调用之后都紧跟PyErr_Occurred()检查并在降档重试前用PyErr_Clear()清除挂起异常避免异常状态污染后续调用。七、KBEngine 中的综合实战PyLong 贯穿属性类型系统与内存流7.1 实体属性类型entitydef/datatype.cppKBEngine 的实体属性定义支持INT8/INT16/INT32/INT64/UINT8/UINT16/UINT32/UINT64等整型。以datatype.cpp为证每个类型都实现四件事isSameType(PyObject*)用PyLong_Check初筛 PyLong_As*试转换 PyErr_Occurred()终判parseDefaultStr(std::string)把.def文件中的默认值字符串解析为数值用PyLong_FromUnsignedLongLong等构造 Python 对象addToStream(MemoryStream*, PyObject*)PyLong_As*取 C 值后写入MemoryStream序列化到网络包/存档createFromStream(MemoryStream*)从流读 C 值后用PyLong_From*还原 Python 对象。INT64与UINT64的处理逻辑几乎相同仅把PyLong_AsUnsignedLongLong/PyLong_FromUnsignedLongLong换成PyLong_AsLongLong/PyLong_FromLongLong并对负值做范围约束。7.2 Python 内存流pyscript/py_memorystream.cppKBEngine 将 CMemoryStream封装给 Python 层使用脚本可自定义网络协议包。在 py_memorystream.cpp 中可以看到一整套完整的整数读写映射写入方向PyLong_AsUnsignedLong/PyLong_AsLong/PyLong_AsUnsignedLongLong/PyLong_AsLongLong按目标宽度取出 C 值读取方向PyLong_FromUnsignedLong/PyLong_FromLong/PyLong_FromUnsignedLongLong/PyLong_FromLongLong把流中读出的 C 值封装回 Python 对象如第 425-467 行的按类型分发逻辑。这再次印证任何要在 C 与 Python 之间传递整数的系统调用都绕不开本节介绍的 From/As API 及其错误处理约定。7.3 其他典型调用点仓库中PyLong_*还广泛出现在 script.cpp、py_compression.cpp、py_gc.cpp、pyurl.cpp 以及服务端 entity.cpp、proxy.cpp、pybots.cpp 等文件中承担参数解析、ID 传递、实体属性收发等职责。八、实践要点与常见陷阱不要忘记PyErr_Occurred()返回值-1/-1.0/NULL均可能是合法结果只有异常状态能区分。小整数缓存是共享引用-5~256范围内的PyLong_FromLong返回既有对象引用计数 1切勿通过修改其内容改变 1 的值行为未定义。无符号转换与负值PyLong_AsUnsignedLongLong对负值自 Python 3.1 起抛OverflowError需要位模式语义时改用 Mask 系列按2^位宽取模。*AndOverflow与普通接口的选择需要区分超范围与其他错误时用PyLong_AsLongAndOverflow/PyLong_AsLongLongAndOverflow仅需简单转换时用普通接口。PyLong_AsSsize_t/PyLong_AsUnsignedLong/PyLong_AsSize_t要求精确类型文档明确这些接口要求参数必须是PyLongObject实例不自动调用__int__传入其他类型需先自行转换。字符串解析注意进制定位base0时0123这类前导零十进制会抛ValueError下划线分隔符与进制前缀之后、数字之间的单个下划线会被忽略。内存所有权所有PyLong_From*都返回新引用小整数除外用完需Py_DECREF或交给Py_BEGIN_ALLOW_THREADS等所有权管理KBEngine 中使用S_RELEASE(pyval)宏统一释放。跨平台位宽差异long在 Windows 为 32 位、Linux 为 64 位Py_ssize_t、size_t随平台变化。需要固定 64 位语义时优先使用long long/unsigned long long系接口。九、总结PyLong C API 是 CPython 整数对象的标准接口集PyLong_Check/PyLong_CheckExact负责类型判定PyLong_From*负责从各类 C 值、字符串、指针构造整数PyLong_As*负责把整数提取回各类 C 值并妥善处理溢出与错误。在 KBEngine 中这套 API 是实体属性类型系统datatype.cpp、Python 内存流py_memorystream.cpp以及服务端与脚本交互的基石。理解其任意精度 小整数缓存 哨兵错误值 PyErr_Occurred()判定的设计哲学即可在自研扩展模块或引擎二次开发中写出健壮、零内存泄漏的桥接代码。如需查阅全部接口的权威定义可直接阅读仓库内嵌文档 kbe/src/lib/python/Doc/c-api/long.rst并结合 longobject.h声明与 longobject.c实现交叉验证。赞分享游戏开发后端【免费下载链接】kbengineA MMOG engine of server.项目地址https://gitcode.com/gh_mirrors/kb/kbengine点击查看免费下载相关推荐KBEngine 内嵌 CPython 的单元对象Cell ObjectsC API 完全指南KBEngine 内嵌 CPython 的单元对象Cell ObjectsC API 完全指南 导读 单元对象Cell Object是 CPython游戏开发后端CPython 整数对象 C API 完全指南PyLong 转换、导出布局与 PyLongWriter 深入解析CPython 整数对象 C API 完全指南PyLong 转换、导出布局与 PyLongWriter 深入解析 在 CPython C 扩展开发中Pyth编程语言语言运行时解释器标准库mmcv.visualization 可视化模块完全指南图像、检测框与光流展示 API 详解mmcv.visualization 可视化模块完全指南图像、检测框与光流展示 API 详解 本文以 OpenMMLab 计算机视觉基础库 mmcv 的可视化游戏开发后端上一篇如何使用Larastan确保Eloquent模型关系的类型安全完整指南下一篇yuzu Switch 模拟器快速上手指南免费 5 分钟跑通 NSP/XCI 首次出画面创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考