One can identify several problems with your Python/C code:
PyObject_IsInstance
takes a class, not a string, as its second argument.There is no code dedicated to reference counting. New references, such as those returned by
PyObject_GetAttr
are never released, and borrowed references obtained withPyList_GetItem
are never acquired before use. Mixing C++ exceptions with otherwise pure Python/C aggravates the issue, making it even harder to implement correct reference counting.Important error checks are missing.
PyString_FromString
can fail when there is insufficient memory;PyList_GetItem
can fail if the list shrinks in the meantime;PyObject_GetAttr
can fail in some circumstances even afterPyObject_HasAttr
succeeds.
Here is a rewritten (but untested) version of the code, featuring the following changes:
The utility function
GetExpressionTreeClass
obtains theExpressionTree
class from the module that defines it. (Fill in the correct module name formy_module
.)Guard
is a RAII-style guard class that releases the Python object when leaving the scope. This small and simple class makes reference counting exception-safe, and its constructor handles NULL objects itself.boost::python
defines layers of functionality in this style, and I recommend to take a look at it.All
Python_exception
throws are now accompanied by setting the Python exception info. The catcher ofPython_exception
can therefore usePyErr_PrintExc
orPyErr_Fetch
to print the exception or otherwise find out what went wrong.
The code:
class Guard {
PyObject *obj;
public:
Guard(PyObject *obj_): obj(obj_) {
if (!obj)
throw Python_exception("NULL object");
}
~Guard() {
Py_DECREF(obj);
}
};
PyObject *GetExpressionTreeClass()
{
PyObject *module = PyImport_ImportModule("my_module");
Guard module_guard(module);
return PyObject_GetAttrString(module, "ExpressionTree");
}
void VisitTree(PyObject* py_tree) throw (Python_exception)
{
PyObject *cls = GetExpressionTreeClass();
Guard cls_guard(cls);
PyObject* list = PyObject_GetAttrString(py_tree, "children");
if (!list && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear(); // hasattr does this exact check
return;
}
Guard list_guard(list);
Py_ssize_t size = PyList_Size(list);
for (Py_ssize_t i = 0; i < size; i++) {
PyObject* child = PyList_GetItem(list, i);
Py_XINCREF(child);
Guard child_guard(child);
// check if child is class instance or number (terminal)
if (PyInt_Check(child) || PyLong_Check(child) || PyString_Check(child))
; // terminal - do nothing for now
else if (PyObject_IsInstance(child, cls))
VisitTree(child);
else {
PyErr_Format(PyExc_TypeError, "unrecognized %s object", Py_TYPE(child)->tp_name);
throw Python_exception("unrecognized object from python");
}
}
}