为什么我在 C++ 中导入模块后无法访问我的 Python 函数?

Why am I unable to access my Python function after importing its module in C++?

我正在尝试从用户定义的 Python 模块 Spam 中调用函数 print_stuff,它是 class Spam 的成员C++

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int main()
{
    Py_Initialize();

    //modify python module search path
    auto modulePath = /* hardcoded search path for module */
    PyObject* sysPath = PySys_GetObject("path");
    PyList_Append(sysPath, PyUnicode_FromString(modulePath));

    PyObject* pModule = PyImport_ImportModule("Spam");
    PyObject* pDict = PyModule_GetDict(pModule);
    PyObject* pFunc = PyObject_GetAttrString(pDict, "print_stuff");
    
    if (pFunc != NULL)
        PyObject_CallObject(pFunc, NULL);
    else
        PyErr_Print();
    
    return 1;
}

我的Python模块由2个文件组成; Spam.py__init__.py 都位于同一目录 /Spam 中,该目录是包含我的 VS 解决方案和 C++ 代码的目录的子文件夹。

Spam.py

class Spam:
    def __init__(self):
        pass
    def print_stuff(self):
        print((1,2,3))

__init__.py

import Spam as Spam

当我 运行 我的 C++ 代码时,出现以下错误:AttributeError: 'dict' object has no attribute 'print_stuff'

我已经尝试从 PyModule_GetDict(pModule) 中获取字典 returned 的内容作为字符串,并确认它没有提到函数。

我查找了 PyModule_GetDict 的文档,其中说它 return 与您在 Python 中的模块上调用 __dict__ 的字典相同。当我尝试在 __init__.py 中调用 Spam.__dict__ 时,我得到了相同的结果,我的函数丢失了,但是当我尝试 Spam.Spam.__dict__ 时,我得到了一个包含该函数的字典。

据此我决定将导入语句更改为 from Spam import Spam 以便 Spam.__dict__ 现在可以包含我的函数,它确实这样做了。但是,当我 运行 我的 C++ 代码时,没有任何变化。我仍然从我的字典中得到同样的错误并且仍然无法调用我的函数。

我确实想知道这是否是我的 C++ 代码中没有 class 实例的问题,所以我也尝试删除周围的 class 并拥有 Spam.py只包含函数定义,然后导入它,但是,我又遇到了同样的错误。

我有预感这是某种命名空间问题,否则我不明白 PyModule_GetDict__dict__ 怎么可能 return 同一模块的不同字典,但老实说我不知道从这里去哪里。

我的 class Spam 不被认为是我试图从 C++ 导入的模块的一部分吗?我的 __init__.py 是否遗漏了一些东西,或者我可能需要在 C++ 中包含另一条语句以在导入模块后导入 class 还是我一开始没有正确导入模块?

我以前见过类似的问题,但它们往往出现在引发异常、搜索路径不正确或先决条件值 return 为 NULL 的情况下。我对 C++ 相当有经验,但对 Python 是新手,对 Python/C API 还是新手,尽管我已经阅读了涵盖 API 基础知识以及如何使用模块的文档在 Python.

工作

首先,我认为您不需要 spam.py 中的最后两行。我将其简化为:

class Spam:
    def __init__(self):
        pass
    def print_stuff(self):
        print((1,2,3))

现在,让我们测试模块:

>>> import spam
>>> s = spam.Spam()
>>> s.print_stuff()
(1, 2, 3)

到目前为止,还不错。让我们看看我们是否可以在 C++ 中使用它。这是您程序的工作版本:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include <stdlib.h>

int main()
{
    Py_Initialize();

    //modify python module search path
    auto modulePath = ".";
    PyObject* sysPath = PySys_GetObject("path");
    PyList_Append(sysPath, PyUnicode_FromString(modulePath));

    PyObject* pModule = PyImport_ImportModule("spam");

    PyObject* spam_class = PyObject_GetAttrString(pModule, "Spam");
    if( !spam_class ) { PyErr_Print(); return EXIT_FAILURE; }

    PyObject* spam = PyObject_CallObject(spam_class, NULL);
    if( !spam ) { PyErr_Print(); return EXIT_FAILURE; }
    
    PyObject* pFunc = PyObject_GetAttrString(spam, "print_stuff");

    if (pFunc != NULL)
        PyObject_CallObject(pFunc, NULL);
    else
        PyErr_Print();
    
    return 1;
}

我将模块命名为 spam.py,小写。模块对象有一个 Spam class 作为属性。我们得到 class 并 调用它 来创建一个对象,就像我们在 Python:

中所做的那样
>>> s = spam.Spam()

现在我们有了对象。它有方法,这些方法是属性,我们可以像往常一样用 PyObject_GetAttrString 获得。剩下的你就知道了。

我已经有几年没有接触过这些东西了,我的经验来自另一面,让 Python 可以访问 C 模块。我通过在 Python 解释器中使用 dir 来完成上面的示例,直到我得到我需要的东西。如果您“像口译员一样思考”,您可能会发现这一切都更有意义。