How to define class methods in Python C API? - python

I was trying to create a Python class definition in C++ code and access it in Python. However, the function is called but the parameters are not received correctly. Please help me in doing this properly.
#include <iostream>
#include <Python.h>
using namespace std;
#include <Python.h>
static PyObject* MyClass__Init(PyObject *self, PyObject *args)
{
cout << "MyClass__Init Called" << endl;
Py_INCREF(Py_None);
return Py_None;
};
static PyObject* MyModule__Start(PyObject *self, PyObject *args)
{
const char* zBuff;
if (PyArg_ParseTuple(args, "s", &zBuff))
cout << "MyModule Start Called with parameter " << zBuff << endl;
else
cout << "MyModule Start ERROR" << endl;
Py_INCREF(Py_None);
return Py_None;
};
static PyObject* MyClass__Start(PyObject *self, PyObject *args)
{
const char* zBuff;
if (PyArg_ParseTuple(args, "s", &zBuff))
cout << "MyClass Start Called with parameter" << zBuff << endl;
else
cout << "MyClass Start ERROR" << endl;
Py_INCREF(Py_None);
return Py_None;
};
static PyMethodDef pModuleMethods[] =
{
{"Start", MyModule__Start, METH_VARARGS, ""},
{NULL, NULL, 0, NULL}
};
static PyMethodDef pClassMethods[] =
{
{"__init__", MyClass__Init, METH_VARARGS, ""},
{"Start", MyClass__Start, METH_VARARGS, ""},
{NULL, NULL, 0, NULL}
};
void Start()
{
Py_Initialize();
/* create a new module and class */
PyObject *pClassDic = PyDict_New();
PyObject *pClassName = PyString_FromString("MyClass");
PyObject *pClass = PyClass_New(NULL, pClassDic, pClassName);
PyObject *pModule = Py_InitModule("MyModule", pModuleMethods);
PyObject *pModuleDic = PyModule_GetDict(pModule);
/* add methods to class */
for (PyMethodDef* pDef = pClassMethods; pDef->ml_name != NULL; pDef++)
{
PyObject *pFunc = PyCFunction_New(pDef, NULL);
PyObject *pMethod = PyMethod_New(pFunc, NULL, pClass);
PyDict_SetItemString(pClassDic, pDef->ml_name, pMethod);
}
PyDict_SetItemString(pModuleDic, "MyClass", pClass);
PyRun_SimpleString("import MyModule\n"
"MyModule.Start('Hello Module')\n"
"myObj = MyModule.MyClass()\n"
"myObj.Start('Hello Class')\n");
Py_Finalize();
};
int main()
{
Start();
};
Output is,
MyModule Start Called with parameter Hello Module
MyClass__Init Called
MyClass Start ERROR
The Module function is called without any issue, but the class method is called without the proper input variable.

It appears that the parameter to self is always NULL and instead - for class methods - the reference to self is passed within the argument list.
So, to parse the arguments of a class method you need to parse the reference to self, too.
Since Python 2.6 you can provide a list of format specifiers to PyArg_ParseTuple. The number of format specifiers has to fit the number of arguments that are passed to the function (see https://docs.python.org/2/c-api/arg.html under (items) (tuple) [matching-items]).
By modifying your MyClass__Start function to parse an additional parameter, you are able to parse and print both arguments to inspect them.
For me, the following code
static PyObject* MyClass__Start(PyObject *self, PyObject *args)
{
PyObject* argListSelf;
const char* zBuff;
if (PyArg_ParseTuple(args, "Os", &argListSelf, &zBuff)) {
cout << "MyClass Start Called with parameters " <<
cout << PyString_AsString(PyObject_Str(argListSelf)) <<
cout << " and " << zBuff << endl;
cout << "self " << PyString_AsString(PyObject_Str(self)) << endl;
}
else {
if(PyErr_Occurred())
PyErr_Print();
cout << "MyClass Start ERROR" << endl;
}
Py_INCREF(Py_None);
return Py_None;
};
results in
MyModule Start Called with parameter Hello Module
MyClass__Init Called
MyClass Start Called with parameters \
0x602428<?.MyClass instance at 0x7f484a333200>0x602428 and HelloClass
self <NULL>
Note that I printed the pointer value of self, which is NULL.
I also added
if(PyErr_Occurred())
PyErr_Print();
which I would always add for debugging purposes.

Related

The introduction of Python in C + + and the error in xstring

C PART
#include <stdlib.h>
#include <iostream>
#include "Python.h"
using namespace std;
char* PyCall(const char* a, const char* b, const char* c) {
Py_Initialize();
if (!Py_IsInitialized())
{
}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
PyObject* pFunc1 = NULL;
PyObject* pFunc2 = NULL;
PyObject* pFunc3 = NULL;
PyObject* pModule = PyImport_ImportModule(a);
if (pModule == NULL)
{
cout << "notfind";
}
pFunc3 = PyObject_GetAttrString(pModule, b);
PyObject* args3 = PyTuple_New(1);
PyObject* args2 = PyBytes_FromString(c);
PyTuple_SetItem(args3, 0, args2);
PyObject* pRet = PyObject_CallObject(pFunc3, args3);
char* result = NULL;
if (pRet)
{
result = PyBytes_AsString(pRet);
}
return result;
}
int main()
{
char* res = PyCall("mytest", "codetest", "{'title':'Task Manager'}");
cout << res;
}
Python Part
def codetest(title):
import win32gui
import win32api
import json
dic = json.loads(title)
a = win32gui.FindWindow(None,dic["title"])
return str(a)
The basic Python library was imported successfully, but a runtime error occurred
enter image description here
Exception thrown at 0x00007ff680271103 (in pycode. Exe): 0xc0000005: an access violation occurred while reading location 0x000000000000000.
This problem has been resolved. The problem of the return value during the call resulted in cout error, and the parameter passed in python is bytes that needs to be decoded and transcoded. I didn’t read the C-API documentation carefully.

Embedding python in C++ and extracting c++ types

I'm trying to embed simple python instructions in my c++ program.
I'm unable to extract c++ types from the python object types...
Would appreciate any help!
Sample Program:
#include <iostream>
#include <Python.h>
using namespace std;
int main()
{
Py_Initialize();
auto pModule = PyImport_ImportModule("math");
auto pFunc = PyObject_GetAttrString(pModule, "sin");
auto pIn = Py_BuildValue("(f)", 2.);
auto pRes = PyObject_CallObject(pFunc, pIn);
auto cRes = ???;
cout << cRes << endl;
Py_Finalize();
}
The program should simply print the result for sin(2).
You'll want to know what type(s) to expect from the function call, including errors... If the function raised an exception, the PyObject_CallObject should return NULL, so check for that first:
if (!pRes) {
PyErr_Print();
// don't do anything else with pRes
}
Otherwise, you can check for and interpret each type you might expect from the Python function call:
if (pRes == Py_None) {
cout << "result is None" << endl;
} else if (PyFloat_Check(pRes)) {
auto cRes = PyFloat_AsDouble(pRes);
cout << cRes << endl;
} else if (<other checks>) {
// Handle other types
} else {
cout << "Unexpected return type" << endl;
}
In the case of your math.sin() call, you can probably safely assume either an exception or a PyFloat return.

Python crashes on returning an C++ map value

i am writing a Test to keep Data in a C++ map. I am using the Python C-Api.
Adding int values and getting the length of the map is no problem.
But when i get a value from the map and want to return it to Python than the Interpreter crashes.
This is my Code:
//some includes
int VerboseLog = 99;
typedef map<const char*, int> SessionKeeper;
static SessionKeeper openSessions;
static PyObject* getSession(PyObject* self, PyObject* args) {
cout << "get" << endl;
const char *key;
if (!PyArg_ParseTuple(args, "z*", &key)) {
PyErr_SetString(PyExc_Exception, "UUID konnte nicht ausgelesen werden");
PyErr_PrintEx(1);
return NULL;
}
int r = openSessions.at(key);
return Py_BuildValue("i", r);
}
static PyObject *addSession(PyObject* self, PyObject* args) {
cout << "Hello" << endl;
const char *key;
int toAppend;
if (!PyArg_ParseTuple(args, "si", &key, &toAppend)) {
PyErr_SetString(PyExc_Exception, "Ein Parameter konnte nicht ausgelesen werden");
PyErr_PrintEx(1);
return NULL;
}
openSessions[key] = toAppend;
cout << openSessions.size() << endl;
cout << openSessions[key] << endl;
Py_RETURN_NONE;
}
static PyObject* length(PyObject *self){
cout << "length: ";
int i = openSessions.size();
return Py_BuildValue("i",i);
}
static PyMethodDef SessionKeeper_methods[] = {
/*Note the third entry (METH_VARARGS). This is a flag telling the interpreter the calling convention
to be used for the C function. It should normally always be METH_VARARGS or METH_VARARGS | METH_KEYWORDS;
a value of 0 means that an obsolete variant of PyArg_ParseTuple() is used.*/
{ "length", (PyCFunction)length, METH_VARARGS, "return length of a Session" },
{ "addSession", (PyCFunction)addSession, METH_VARARGS, "add a Session." },
{ "getSession", (PyCFunction)getSession, METH_VARARGS, "get a Session" },
{ NULL }
};
static PyModuleDef sessionKeeperMod = {
PyModuleDef_HEAD_INIT,
u8"SessionKeeper",
NULL,
-1,
SessionKeeper_methods
};
static PyModuleDef CpluplusModules_ModDef = {
PyModuleDef_HEAD_INIT,
u8"CPlusPlusMouldes",
u8"Enthält alle Erweietrungen",
-1,
NULL, NULL, NULL, NULL, NULL
};
PyMODINIT_FUNC PyInit_CPlusPlusModules(void) {
PyObject* m, *sessionMod;
if (PyType_Ready(&TAModulType) < 0)
return NULL;
if (PyType_Ready(&DatabaseReader_Type) < 0)
return NULL;
if (!(sessionMod = PyModule_Create(&sessionKeeperMod))) {
cout << "fail" << endl;
return NULL;
}
m = PyModule_Create(&CpluplusModules_ModDef);
if (m == NULL)
return NULL;
Py_INCREF(&TAModulType);
Py_INCREF(&DatabaseReader_Type);
PyModule_AddObject(m, "TAModul", (PyObject *)&TAModulType);
PyModule_AddObject(m, "DBReader", (PyObject *)&DatabaseReader_Type);
PyModule_AddObject(m, "SessionKeeper", sessionMod);
return m;
}
The other Modules (DBReader and TAModule) workes fine. The goal will be to safe PythonObjects (containing DbReader Objects and TAModul Objects) in the map.
But bacck to my question why do the getSession crashes the Interpreter on return? And why do the length function works fine.
map<const char*,...> will match based on the address of the string rather than the contents. Therefore it's pretty likely that at will fail and throw an C++ out_of_range exception.
You should start by wrapping the line
int r = openSessions.at(key);
with a try {} catch block, to stop the out_of_range exception propagating through the Python interpreter (which isn't C++ so can't handle it).
You should then change the map to match the key on the string contents. The easiest way would be to use map<std::string,int>.

exporting c++ function for python, pass several strings as parameters

I'm trying to export some C++ functions into python, and it's working pretty well most of the time, but I'm having issues when a function takes more than on string in parameters, the first one is always ok, but the other ones are always gibberish.
C++ code:
#include <Python.h>
#include <iostream>
PyObject* wrap_one_string(PyObject *self, PyObject *args)
{
char* str;
if (!PyArg_ParseTuple(args,"s:wrap_one_string",&str))
{
return nullptr;
}
std::cout << str << std::endl;
return Py_None;
}
PyObject* wrap_two_string(PyObject *self, PyObject *args)
{
char* str1, str2;
if (!PyArg_ParseTuple(args,"ss:wrap_one_string", &str1, &str2))
{
return nullptr;
}
std::cout << str1 << std::endl << str2 << std::endl;
return Py_None;
}
static PyMethodDef exampleMethods[] = {
{ "one_string", wrap_one_string, METH_VARARGS, nullptr },
{ "two_string", wrap_two_string, METH_VARARGS, nullptr },
{ nullptr, nullptr, 0, nullptr}
};
extern "C"
{
__declspec(dllexport)
void initexample()
{
PyObject *m;
m = Py_InitModule("example", exampleMethods);
}
}
python code:
import example
example.one_string("ab")
example.two_string("abcd", "efgh")
And the result is:
ab
abcd
È
The second string parameter is always a weird character.
Any idea where this might come from?
Thanks
Ah, nevermind, dumb mistake from my part
char* str1, str2;
should be
char* str1, *str2;
Too bad it compiled, though it did give an error using
str2 = PyString_AsString(PyTuple_GET_ITEM(args, 1))
to access the second string.
One small note, remember to Py_INCREF(Py_None); before returning Py_None, as stated in the reference [https://docs.python.org/2/extending/extending.html#back-to-the-example]
Or use Py_RETURN_NONE macro [https://docs.python.org/2/c-api/none.html#c.Py_RETURN_NONE]

Refreshing a import in C embedded python

I have a C code which has embedded python in it using "Python.h" It works fine without any errors - But it doesn't completely do what I want it to.
What it does : After the C code starts running, it ignores all changes which I make to the python file until I restart the C code.
What I want : While the C code is running, if i make changes to the python file, it should start running the new code.
I tried using the function PyImport_ReloadModule in everytime before calling the function, but it does not work. Am I doing something wrong ?
My current code :
#include "Strategy.h"
#undef _DEBUG /* Link with python24.lib and not python24_d.lib */
#include <Python.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
PyObject *pName, *pModule, *pDict, *pFunc;
PyObject *pArgs, *pValue;
void import_py() {
pName = PyString_FromString("main");
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule == NULL) {
cout << "ERR : Unable to load main.py\n";
return;
} else {
cout << "OK : Loaded main.py\n";
}
if ( PyObject_HasAttrString(pModule, "main") ) {
cout << "OK : main.py has function main\n";
} else {
cout << "ERR : main.py has no function main\n";
return;
}
pFunc = PyObject_GetAttrString(pModule, "main");
if ( pFunc == NULL ) {
cout << "OK : main.py's function main gave NULL when trying to take it\n";
return;
}
}
void remove_py() {
Py_XDECREF(pArgs);
Py_XDECREF(pModule);
Py_XDECREF(pFunc);
Py_Finalize();
}
void Construct() {
Py_Initialize();
import_py();
}
void Destruct() {
if ( pModule || pFunc ) {
remove_py();
}
}
void Loop () {
if ( ! ( pModule && pFunc ) ) {
cout << "Looped. But python values are null\n";
return;
}
cout << "Loop : ";
pArgs = PyTuple_New(2); // Create a tuple to send to python - sends 1,2
PyTuple_SetItem(pArgs, 0, PyInt_FromLong(1));
PyTuple_SetItem(pArgs, 1, PyInt_FromLong(2));
pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
double t = 0; // Get the 2 return values
t = PyFloat_AsDouble(PyTuple_GetItem(pValue, 0));
cout << t << ", ";
t = PyFloat_AsDouble(PyTuple_GetItem(pValue, 1));
cout << t;
cout << "\n";
}
void main() {
Construct();
while(1) { // Using an infinite loop for now - to test
pModule = PyImport_ReloadModule(pModule);
Loop();
}
Destruct();
}
I found the issue.
Even after getting the new module with pModule = PyImport_ReloadModule(pModule) the p
variable pFunc doesn't get automatically updated. So, the variable pFunc is still referencing the old module !
hence, every variable needs to be got again. Like so :
void main() {
Construct();
while(1) { // Using an infinite loop for now - to test
pModule = PyImport_ReloadModule(pModule);
pFunc = PyObject_GetAttrString(pModule, "main");
Loop();
}
Destruct();
}
One thing I am not sure about here is whether DECREF should be made to the pFunc which is referencing the old pModule.

Categories