所以我创建了这个
main.py
:
#!/usr/bin/python3
import ctypes
foo = 1
# Should print 2, but prints 1
def print_foo():
global foo
print(foo)
def main():
global foo
foo = 2
dll = ctypes.PyDLL("./foo.so")
foo = dll.foo
foo.restype = None
foo()
if __name__ == "__main__":
main()
还有这个
foo.c
:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <assert.h>
#define CHECK_PYTHON_ERROR() {\
if (PyErr_Occurred()) {\
PyErr_Print();\
fprintf(stderr, "Error detected in foo.c:%d\n", __LINE__);\
exit(EXIT_FAILURE);\
}\
}
void foo(void) {
PyObject *module = PyImport_ImportModule("main");
CHECK_PYTHON_ERROR();
assert(module);
PyObject *print_foo = PyObject_GetAttrString(module, "print_foo");
CHECK_PYTHON_ERROR();
assert(print_foo);
PyObject *result = PyObject_CallObject(print_foo, NULL);
CHECK_PYTHON_ERROR();
assert(result);
}
我已经用
foo.c
将 foo.so
编译为 gcc foo.c -o foo.so -shared -fPIC -Wall -Wextra -Wpedantic -Wfatal-errors -g -lpython3.13 -I/home/trez/.pyenv/versions/3.13.0/include/python3.13 -L/home/trez/.pyenv/versions/3.13.0/lib -ldl -Wl,-rpath,/home/trez/.pyenv/versions/3.13.0/lib -lm
,其中最后的标志来自运行 python3.13-config --includes --ldflags
。
运行
python3.13 main.py
打印 1。这意味着 foo = 2
语句在打印 foo
的实例中没有发生,这是不好的。
该程序当前打印 1 的原因是因为
PyImport_ImportModule()
(docs) 调用本质上创建了一个新环境,而不是重用原始环境。
将其更改为
PyImport_AddModule()
(docs) 或 PyImport_AddModuleRef()
(docs) 将打印以下内容:
AttributeError: module 'main' has no attribute 'print_foo'
Error detected in foo.c:20
如果我们查看
PyImport_AddModule
它的文档,我们只会看到:
与 PyImport_AddModuleRef() 类似,但返回借用的引用。
如果我们查看
PyImport_AddModuleRef
它的文档,就会看到以下片段:
该函数不会加载或导入模块;如果模块尚未加载,您将得到一个空的模块对象。使用 PyImport_ImportModule() 或其变体之一导入模块。
所以这似乎暗示
AttributeError: module 'main' has no attribute 'print_foo'
错误是由模块未加载引起的。但这对我来说没有意义,因为使用 PyImport_AddModule
/PyImport_AddModuleRef
并将其添加到 assert(module);
行之后:
PyObject *g = PyEval_GetGlobals();
CHECK_PYTHON_ERROR();
PyObject *g_repr = PyObject_Repr(g);
CHECK_PYTHON_ERROR();
const char *s = PyUnicode_AsUTF8(g_repr);
CHECK_PYTHON_ERROR();
printf("s: '%s'\n", s);
打印这个:
s: '{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x6ffc309a5e00>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/trez/Programming/ctypes-run-bug/main.py', '__cached__': None, 'ctypes': <module 'ctypes' from '/home/trez/.pyenv/versions/3.13.0/lib/python3.13/ctypes/__init__.py'>, 'foo': <_FuncPtr object at 0x6ffc30946450>, 'print_foo': <function print_foo at 0x6ffc308231a0>, 'main': <function main at 0x6ffc3088f4c0>}'
AttributeError: module 'main' has no attribute 'print_foo'
Error detected in foo.c:28
注意第一行末尾附近的
'print_foo': <function print_foo at 0x6ffc308231a0>
,这意味着函数 is 已找到。
我对这里发生的情况的猜测是这些全局变量是来自原始Python实例的,但是
PyImport_AddModule("main")
调用不知何故无法找到该实例。
如何保持
main.py
不变,同时以打印 2 而不是 1 的方式修改 foo.c
?干杯。
juanpa.arrivillaga 给出了很好的建议,让 C 代码通过检查
sys.modules
的 C 等效项来获取主模块,并且该解决方案确实成功打印了 2:
#!/usr/bin/python3
import ctypes
foo = 1
def print_foo():
global foo
print(foo)
def main():
global foo
foo = 2
dll = ctypes.PyDLL("./foo.so")
bar = dll.bar
bar.restype = None
bar()
if __name__ == "__main__":
main()
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <assert.h>
#define CHECK_PYTHON_ERROR() {\
if (PyErr_Occurred()) {\
PyErr_Print();\
fprintf(stderr, "Error detected in foo.c:%d\n", __LINE__);\
exit(EXIT_FAILURE);\
}\
}
void bar(void) {
PyObject *modules = PySys_GetObject("modules");
CHECK_PYTHON_ERROR();
assert(modules);
PyObject *main_object = PyDict_GetItemString(modules, "__main__");
CHECK_PYTHON_ERROR();
assert(main_object);
PyObject *print_foo = PyObject_GetAttrString(main_object, "print_foo");
CHECK_PYTHON_ERROR();
assert(print_foo);
PyObject *result = PyObject_CallObject(print_foo, NULL);
CHECK_PYTHON_ERROR();
assert(result);
}
MarkTolonen 让我意识到我不小心用
foo
行覆盖了我的 foo = dll.foo
值 2。我已将其更改为 bar = dll.bar; bar.restype = None; bar()
,并将 void foo(void) { ... }
重命名为 void bar(void) { ... }
。
正如 juanpa.arrivillaga 提到的,如果
print_foo()
和 foo = 1
被移动为 helper.py
,并且我将 main()
更改为看起来像 def main(): helper.baz()
(其中 baz()
看起来像旧的 main()
),然后我得到AttributeError: module '__main__' has no attribute 'print_foo'
。这对我来说很好,因为我不介意对 print_foo()
所在的模块名称进行硬编码。