Python 3 C扩展迁移疑问:PyImport_ExtendInittab相关问题咨询
Python 3嵌入扩展:模块注册迁移问题解答
先聊聊我对你遇到的问题的理解:之前在C应用里嵌入并扩展Python 2.7时,你用PyModule_Create创建模块,手动给它添加成员(包括子模块),还把顶层模块塞进PyEval_GetBuiltins()里——这种操作在Py2里虽不规范但能跑,可迁移到Py3后直接报错:ModuleNotFoundError: No module named 'foo.bar'; 'foo' is not a package。后来你查阅了PyImport_ExtendInittab的示例,有两个疑问,我来逐一解答:
1. Inittab的含义是什么?为什么不叫PyImport_ExtendBuiltins?
Inittab其实是Initialization Table(初始化表)的缩写,本质是Python解释器用来记录所有内置扩展模块初始化函数的列表。当Python第一次尝试导入某个模块时,会优先查询这个表:如果找到对应模块名的初始化函数,就调用它来创建并返回模块对象,这是Python扩展模块被导入的标准入口。
至于为什么不叫PyImport_ExtendBuiltins,这俩完全是两个不同的东西:
- 你之前往
PyEval_GetBuiltins()里塞模块的操作,是把模块直接扔进了Python的内置命名空间——这相当于绕开了正常的模块导入机制,属于“野路子”。builtins是用来存放print、len这类内置函数/对象的地方,根本不是给扩展模块留的位置,Py2对这种操作比较宽容,但Py3收紧了规则,直接触发了报错。 - 而
PyImport_ExtendInittab是在模块导入的底层注册环节添加模块,让Python的导入系统能正常识别它是一个合法模块,走标准的import流程。这才是嵌入扩展模块的规范做法,能和Python的模块体系完全兼容。
2. 能否通过PyImport_ExtendInittab创建包含子模块的包?
当然可以,但不能像Py2那样直接把子模块挂到父模块上就完事,得严格遵循Py3的包规则来做,核心是要让父模块被识别为包,具体步骤如下:
- 首先,注册父模块
foo时,要把它变成一个包。Python判断一个模块是不是包的核心依据是:是否拥有__path__属性。所以在创建foo模块的初始化函数里,你需要给模块对象添加一个__path__属性(哪怕是空列表也行),比如用PyModule_AddObject()添加一个值为[]的__path__。 - 然后,把子模块
foo.bar单独通过PyImport_ExtendInittab注册。注意Py3里扩展模块的初始化函数命名规则是PyInit_<模块全名>,这里的模块全名要用下划线连接,也就是PyInit_foo_bar(不能用点)。 - 当用户执行
from foo.bar import bas时,Python会先导入foo,发现它有__path__,认定它是包,接着会在包的路径下查找foo.bar,这时候就会命中Inittab里的PyInit_foo_bar函数,创建并加载子模块。
这样整个包结构就能被Python的导入系统正常识别,不会再出现之前的报错了。
内容的提问来源于stack exchange,提问作者Daniel Stephens
相关产品推荐
相关产品推荐

