Reputation: 13
I am attempting to call a function print_stuff
which is a member of the class Spam
from a user-defined Python module Spam
in 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;
}
My Python module consists of 2 files; Spam.py
and __init__.py
both located in same directory /Spam
which is a subfolder of the directory containing my VS solution and C++ code.
Spam.py
class Spam:
def __init__(self):
pass
def print_stuff(self):
print((1,2,3))
__init__.py
import Spam as Spam
When I run my C++ code I get the following error: AttributeError: 'dict' object has no attribute 'print_stuff'
I have tried getting the contents of the dictionary returned from PyModule_GetDict(pModule)
as a string and confirmed that it doesn't mention the function.
I looked up the docs for PyModule_GetDict
which says that it returns the same dictionary as you would get calling __dict__
on the module in Python. When I tried calling Spam.__dict__
inside of __init__.py
I got the same result, my function was missing, but when I tried Spam.Spam.__dict__
I got a dictionary containing the function.
From this I decided to change the import statement to from Spam import Spam
so that Spam.__dict__
would now include my function, which it did. However, nothing changes when I run my C++ code. I still get the same error from my dictionary and am still unable to call my function.
I did wonder if it was an issue of not having an instance of the class in the my C++ code, so I also tried removing the surrounding class and having Spam.py
just contain the function definition and then importing that but, again, I got the same error.
I have a hunch this is some kind of namespace issue, otherwise I don't see how PyModule_GetDict
and __dict__
could return different dictionaries for the same module, but honestly I don't know where to go from here.
Is my class Spam
not considered part of the module I am trying to import from C++? Is my __init__.py
missing something or perhaps I need to include another statement in C++ to import the class after I have imported the module or have I not correctly imported the module in the first place?
I have seen similar questions before but they tended to be in cases where exceptions were raised, the search path was incorrect, or where one the prerequisite values had returned NULL. I am fairly experienced with C++ but am new to Python and newer still to the Python/C API, although I have read the docs covering the basics of the API and how modules work in Python.
Upvotes: 1
Views: 678
Reputation: 7837
First, I don't think you need the last two lines in spam.py. I reduced it to this:
class Spam:
def __init__(self):
pass
def print_stuff(self):
print((1,2,3))
Now, let's test the module:
>>> import spam
>>> s = spam.Spam()
>>> s.print_stuff()
(1, 2, 3)
So far, so good. Let's see if we can use it from C++. Here's a working version of your program:
#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;
}
I named the module spam.py
, lowercase. The module object has a Spam
class as an attribute. We get that class and call it to create an object, just like we do in Python:
>>> s = spam.Spam()
Now we have an object. It has methods, which are attributes, which we can get with PyObject_GetAttrString
, as usual. You know the rest.
It's been a few years since I worked with this stuff, and my experience was from the other side, making C modules accessible to Python. I worked my way to the above example by using dir
in the Python interpreter until I got what I needed. If you "think like the interpreter" you may find it all makes more sense.
Upvotes: 1