如何让Python调用的共享库访问同一个Python实例的全局变量?

问题描述 投票:0回答:1

所以我创建了这个

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
?干杯。

python shared-libraries ctypes
1个回答
0
投票

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()
所在的模块名称进行硬编码。
    

© www.soinside.com 2019 - 2024. All rights reserved.