【问题标题】:Why am I unable to access my Python function after importing its module in C++?为什么在 C++ 中导入其模块后我无法访问我的 Python 函数?
【发布时间】:2021-07-01 14:55:55
【问题描述】:

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

#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) 返回的字典内容作为字符串,并确认它没有提及该函数。

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

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

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

我有预感这是某种命名空间问题,否则我看不出 PyModule_GetDict__dict__ 如何为同一个模块返回不同的字典,但老实说我不知道​​从哪里开始.

我的课程Spam 不被视为我尝试从 C++ 导入的模块的一部分吗?我的__init__.py 是否遗漏了某些内容,或者我可能需要在 C++ 中包含另一个语句来在我导入模块后导入类,或者我一开始就没有正确导入模块?

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

【问题讨论】:

    标签: python c++ python-3.x python-c-api


    【解决方案1】:

    首先,我认为您不需要 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 作为属性。我们得到那个类并调用它来创建一个对象,就像我们在 Python 中所做的那样:

    >>> s = spam.Spam()
    

    现在我们有了一个对象。它有方法,也就是属性,我们可以像往常一样使用PyObject_GetAttrString 获得。其余的你都知道。

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

    【讨论】:

    • 谢谢,这解决了问题,但我有几个问题。首先,我可能遗漏了一些东西,但您的 spam.py 版本似乎与我的相同,您的意思是 init.py 是不必要的吗?其次,似乎我的问题是试图从模块字典中获取函数,而不是从模块中获取类,然后从类中获取函数。我认为类的成员函数对包含模块实际上是不可见的,这是正确的吗?
    • 是的,afaik init.py 是不必要的。至于“隐形”,不完全是。请记住,在解释器执行 类之前,Python 对象不存在。在那之前,它们只是页面上的文字。类定义是无生命的;它定义了一个实例能够做什么,但是 instance 方法不属于 class。这听起来很奇怪,但是您首先执行该类以创建一个实例。然后就可以调用实例方法了。
    猜你喜欢
    • 1970-01-01
    • 2020-06-20
    • 1970-01-01
    • 2015-04-10
    • 1970-01-01
    • 2015-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多