I am new to the Python/C API and while I got some basic functions to work, I am struggling with this one.
PyObject* sum_elements(PyObject*, PyObject *o)
{
Py_ssize_t n = PyList_Size(o);
long total = 0;
if (n < 0)
{
return PyLong_FromLong(total);
}
PyObject* item;
for (int i = 0; i < n; i++)
{
item = PyList_GetItem(o, i);
if (!PyLong_Check(item)) continue;
total += PyLong_AsLong(item);
}
return PyLong_FromLong(total);
}
Basically this is the function from the introduction on the doc page. It should receive a python list and return the sum of all elements. The function works fine if i pass a list, if I pass something else however i get the error message
SystemError: c:\_work\5\s\objects\listobject.c:187: bad argument to internal function
This situation should be handled by the if (n<0) statement, as n is -1 if the passed object is not a list.
I am binding the function the following way:
static PyMethodDef example_module_methods[] = {
{ "sum_list", (PyCFunction)sum_elements, METH_O, nullptr},
{ nullptr, nullptr, 0, nullptr }
};
Thanks.
The error
SystemError: c:\_work\5\s\objects\listobject.c:187: bad argument to internal function
is actually occurs at
Py_ssize_t n = PyList_Size(o)
Because PyList_Size has an extra check to see whether the object of list type, If not it will call PyErr_BadInternalCall API to raise the SystemError. See the implementation of PyList_Size in listobject.c
PyList_Size(PyObject *op)
{
if (!PyList_Check(op)) {
PyErr_BadInternalCall();
return -1;
}
else
return Py_SIZE(op);
}
The PyErr_BadInternalCall a shorthand for PyErr_SetString(PyExc_SystemError, message), where message indicates that an internal operation (e.g. a Python/C API function) was invoked with an illegal argument.
You should use PyList_Check API to check whether the object is of list type . As per the doc it Return true if object is a list object or an instance of a subtype of the list type.
PyObject* sum_elements(PyObject*, PyObject *o)
{
// Check if `o` is of `list` type, if not raise `TypeError`.
if (!PyList_Check(o)) {
PyErr_Format(PyExc_TypeError, "The argument must be of list or subtype of list");
return NULL;
}
// The argument is list type, perform the remaining calculations.
Py_ssize_t n = PyList_Size(o);
long total = 0;
if (n < 0)
{
return PyLong_FromLong(total);
}
PyObject* item;
for (int i = 0; i < n; i++)
{
item = PyList_GetItem(o, i);
if (!PyLong_Check(item)) continue;
total += PyLong_AsLong(item);
}
return PyLong_FromLong(total);
}
Once this extra check is added, the function call will raise
TypeError: The argument must be of list or sub type of list
when the argument other than list type is supplied.
Related
This is part of the code I wanna wrap:
typedef unsigned char UINT8;
...
void fn(UINT8 data[]);
In a .i file, I included:
From 8.2.2 carrays.i:
%array_class(UINT8, UINT8Array)
To support array write operation:
%typemap(in) UINT8[ANY] ($1_basetype temp[$1_dim0]) {
int i;
if (!PySequence_Check($input)) {
PyErr_SetString(PyExc_ValueError,"Expected a sequence as input");
return NULL;
}
if (PySequence_Length($input) != $1_dim0) {
PyErr_SetString(PyExc_ValueError,"Input sequence size incorrect, should have $1_dim0 ints");
return NULL;
}
for (i = 0; i < $1_dim0; i++) {
PyObject *o = PySequence_GetItem($input,i);
if (PyNumber_Check(o)) {
temp[i] = ($1_basetype)PyInt_AsLong(o);
Py_DECREF(o);
} else {
Py_XDECREF(o);
PyErr_SetString(PyExc_ValueError,"Input sequence elements must be numbers");
return NULL;
}
}
$1 = temp;
}
then, when compiling, my code fails:
example_wrap.cxx:40:15: error: storage size of ‘temp2’ isn’t known
UINT8 temp2[] ;
Please note, that the wrapping works if my function is like:
void fn(UINT8 data[10]);
void fn(UINT8* data);
Is there a way I can tell swig, when there is a UINT8[] to trait it as it is a UINT8 *. Something like this:
%typemap(in) UINT8[] ($1_basetype *temp) {
Thanks,
Pablo
In the comments the OP stated that the function parameter had a fixed size. Let's assume the header file looks like this:
#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif
typedef unsigned char UINT8;
API void fn(UINT8 data[]); // data must be length 300
The OP's typemap works, but doesn't know the size. To let SWIG know that there is a fixed size and still take advantage of the generic typemap, the following can be used:
%module test
%{
#include "test.h"
// Implementation...this would normally be in a .cpp file and linked in.
// Note to see the result an argout typemap would be needed too...
API void fn(UINT8 data[]) {
for(int i = 0; i < 300; ++i)
data[i] += 1;
}
%}
// OP's original generalized array typemap...
%typemap(in) UINT8[ANY] ($1_basetype temp[$1_dim0]) {
int i;
if (!PySequence_Check($input)) {
PyErr_SetString(PyExc_ValueError,"Expected a sequence as input");
return NULL;
}
if (PySequence_Length($input) != $1_dim0) {
PyErr_SetString(PyExc_ValueError,"Input sequence size incorrect, should have $1_dim0 ints");
return NULL;
}
for (i = 0; i < $1_dim0; i++) {
PyObject *o = PySequence_GetItem($input,i);
if (PyNumber_Check(o)) {
temp[i] = ($1_basetype)PyInt_AsLong(o);
Py_DECREF(o);
} else {
Py_XDECREF(o);
PyErr_SetString(PyExc_ValueError,"Input sequence elements must be numbers");
return NULL;
}
}
$1 = temp;
}
// Assume there are lots of other functions in test.h,
// but we don't want to wrap the function that requires
// a fixed-sized array and doesn't declare the size.
%ignore fn;
// Process test.h, but skip wrapping fn.
%include "test.h"
// This "un-ignores" the function and provides a replacement
// that declares the fixed size.
// See http://www.swig.org/Doc3.0/SWIGDocumentation.html#SWIG_rename_ignore
%rename("%s") fn;
void fn(UINT8 temp[300]);
Demo:
>>> import test
>>> test.fn([1,2,3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Input sequence size incorrect, should have 300 ints
>>> test.fn([1]*300) # This works
>>> test.fn('a'*300)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Input sequence elements must be numbers
I want to check if an object is an instance of a certain class. In Python I can do this with instanceof. In C/C++, I found a function named PyObject_IsInstance. But it seems not to work like isinstance.
In detail (also described as sample codes below):
In C++ I defined my custom type My. The type definition is MyType and the object definition is MyObject.
Add MyType to the exported module with name My.
In Python, create a new instance my = My(), and isinstance(my, My) returns True.
While in C++ we use PyObject_IsInstance(my, (PyObject*)&MyType) to check my, and this returns 0, which means my is not an instance of the class defined by MyType.
Full C++ code:
#define PY_SSIZE_T_CLEAN
#include <python3.6/Python.h>
#include <python3.6/structmember.h>
#include <stddef.h>
typedef struct {
PyObject_HEAD
int num;
} MyObject;
static PyTypeObject MyType = []{
PyTypeObject ret = {
PyVarObject_HEAD_INIT(NULL, 0)
};
ret.tp_name = "cpp.My";
ret.tp_doc = NULL;
ret.tp_basicsize = sizeof(MyObject);
ret.tp_itemsize = 0;
ret.tp_flags = Py_TPFLAGS_DEFAULT;
ret.tp_new = PyType_GenericNew;
return ret;
}();
// check if obj is an instance of MyType
static PyObject *Py_fn_checkMy(PyObject *obj) {
if (PyObject_IsInstance(obj, (PyObject *)&MyType)) Py_RETURN_TRUE;
else Py_RETURN_FALSE;
}
static PyMethodDef modmethodsdef[] = {
{ "checkMy", (PyCFunction)Py_fn_checkMy, METH_VARARGS, NULL },
{ NULL }
};
static PyModuleDef moddef = []{
PyModuleDef ret = {
PyModuleDef_HEAD_INIT
};
ret.m_name = "cpp";
ret.m_doc = NULL;
ret.m_size = -1;
return ret;
}();
PyMODINIT_FUNC
PyInit_cpp(void)
{
PyObject *mod;
if (PyType_Ready(&MyType) < 0)
return NULL;
mod = PyModule_Create(&moddef);
if (mod == NULL)
return NULL;
Py_INCREF(&MyType);
PyModule_AddObject(mod, "My", (PyObject *)&MyType);
PyModule_AddFunctions(mod, modmethodsdef);
return mod;
}
Compile this into cpp.so, and test it in Python:
>>> import cpp
>>> isinstance(cpp.My(), cpp.My)
True
>>> cpp.checkMy(cpp.My())
False
METH_VARARGS
This is the typical calling convention, where the methods have the type PyCFunction. The function expects two PyObject* values. The first one is the self object for methods; for module functions, it is the module object. The second parameter (often called args) is a tuple object representing all arguments. This parameter is typically processed using PyArg_ParseTuple() or PyArg_UnpackTuple().
The function signature of Py_fn_checkMy does not match this. It should take two arguments. The first is the module, and this is what you are checking against MyType. The second argument (which you don't actually accept) is a tuple containing the object you passed. You should extract the argument from the tuple and check the type of this.
You'd probably be better using METH_O to specify a single argument instead of extracting arguments from a tuple:
static PyObject *Py_fn_checkMy(PyObject *self, PyObject *obj) {
if (PyObject_IsInstance(obj, (PyObject *)&MyType)) Py_RETURN_TRUE;
else Py_RETURN_FALSE;
}
static PyMethodDef modmethodsdef[] = {
{ "checkMy", (PyCFunction)Py_fn_checkMy, METH_O, NULL },
{ NULL }
};
I've chosen setuptools to use my C/C++ code from python scripts.
One of the phases when building such wrapper is to convert the C/C++ return value into python object.
So far I was able to convert simple primitive values and list of primitive values. However, I wish to extend it to multi-value struct, as shown in the example below.
My main challenge right now is how do I create the python struct representation (PyObject* ret = PyList_New(...);) and I do I set it's values properly with the different types.
I tried to create list of items from the same types (such as std::vector<float>) and manage to set the values properly using Py_BuildValue and PyList_SetItem, but I'm still struggling with the multi types...
typedef struct _fileParams
{
bool valid;
int index;
std::string key;
std::value value;
} fileParams;
FileDataBase * db;
static PyObject *searchFileInDB(PyObject *self, PyObject *args)
{
if (db == NULL)
{
PyErr_SetString(PyExc_RuntimeError, "DB could not be initialized");
return NULL;
}
char* fileName = NULL;
int fileNameSize = 0;
PyArg_ParseTuple(args, "s#", &fileName, &fileNameSize);
try
{
fileParams p;
bool res = db->lookup(fileName, fileNameSize, p);
PyObject* ret = PyList_New(...);
if (res)
{
PyObject* r1 = Py_BuildValue("b", p.valid);
PyList_SetItem(ret, 0, r1);
PyObject* r2 = Py_BuildValue("i", p.index);
PyList_SetItem(ret, 1, r2);
PyObject* r1 = Py_BuildValue("s", p.key);
PyList_SetItem(ret, 2, r3);
PyObject* r1 = Py_BuildValue("s", p.value);
PyList_SetItem(ret, 3, r4);
}
return ret;
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, "failed with C exception");
return NULL;
}
}
You probably want to look into the Dictionary Object: Dictionary Objects
I'm guessing you'd want to set values with PyDict_SetItemString() as per that doc.
HTH
if I call a SWIG-wrapped C/C++ function from Python, is it possible to obtain the current call stack? I would like something similar to the result of ''.join(traceback.format_stack()), but I don't want to pass this from Python to my C/C++ functions because I don't always need it. So I would like to obtain it on the fly and print it if something wrong happens on my C/C++ side.
I figured out a solution following this post, although I still prefer more natural ways of getting the same thing if there is any.
// This is similar to the python code:
// def GetScriptingLanguageCallStack():
// import traceback
// return ''.join(traceback.format_stack())
string GetScriptingLanguageCallStack() {
string result;
PyObject* module_name = PyString_FromString("traceback");
PyObject* pyth_module = PyImport_Import(module_name);
Py_DECREF(module_name);
if (pyth_module != nullptr) {
PyObject* pyth_func = PyObject_GetAttrString(pyth_module, "format_stack");
if (pyth_func != nullptr) {
if (PyCallable_Check(pyth_func)) {
PyObject* pyth_val = PyObject_CallFunctionObjArgs(pyth_func, 0);
if (pyth_val != nullptr) {
if (PyList_Check(pyth_val)) {
const int size = PyList_GET_SIZE(pyth_val);
for (int i = 0; i < size; ++i) {
PyObject* pyth_line = PyList_GET_ITEM(pyth_val, i);
result += PyString_AsString(pyth_line);
}
}
Py_DECREF(pyth_val);
}
}
Py_DECREF(pyth_func);
}
Py_DECREF(pyth_module);
}
return result;
}
By the way, I do not prefer the approach in the linked post which uses frame object, because the line number given is not pointing to the exact line which makes the further function calls, but only on the line containing the function name.
I am trying to figure out how in C extension modules to have a variable (and maybe) quite large number of arguments to a function.
Reading about PyArg_ParseTuple it seems you have to know how many to accept, some mandatory and some optional but all with their own variable. I was hoping PyArg_UnpackTuple would be able to handle this but it seems to just give me bus errors when I try and use it in what appears to be the wrong way.
As an example take the following python code that one might want to make into an extension module (in C).
def hypot(*vals):
if len(vals) !=1 :
return math.sqrt(sum((v ** 2 for v in vals)))
else:
return math.sqrt(sum((v ** 2 for v in vals[0])))
This can be called with any number of arguments or iterated over, hypot(3,4,5), hypot([3,4,5]), and hypot(*[3,4,5]) all give the same answer.
The start of my C function looks like this
static PyObject *hypot_tb(PyObject *self, PyObject *args) {
// lots of code
// PyArg_ParseTuple or PyArg_UnpackTuple
}
Many thinks to yasar11732. Here for the next guy is a fully working extension module (_toolboxmodule.c) that simply takes in any number or integer arguments and returns a list made up of those arguments (with a poor name). A toy but illustrates what needed to be done.
#include <Python.h>
int ParseArguments(long arr[],Py_ssize_t size, PyObject *args) {
/* Get arbitrary number of positive numbers from Py_Tuple */
Py_ssize_t i;
PyObject *temp_p, *temp_p2;
for (i=0;i<size;i++) {
temp_p = PyTuple_GetItem(args,i);
if(temp_p == NULL) {return NULL;}
/* Check if temp_p is numeric */
if (PyNumber_Check(temp_p) != 1) {
PyErr_SetString(PyExc_TypeError,"Non-numeric argument.");
return NULL;
}
/* Convert number to python long and than C unsigned long */
temp_p2 = PyNumber_Long(temp_p);
arr[i] = PyLong_AsUnsignedLong(temp_p2);
Py_DECREF(temp_p2);
}
return 1;
}
static PyObject *hypot_tb(PyObject *self, PyObject *args)
{
Py_ssize_t TupleSize = PyTuple_Size(args);
long *nums = malloc(TupleSize * sizeof(unsigned long));
PyObject *list_out;
int i;
if(!TupleSize) {
if(!PyErr_Occurred())
PyErr_SetString(PyExc_TypeError,"You must supply at least one argument.");
return NULL;
}
if (!(ParseArguments(nums, TupleSize, args)) {
free(nums);
return NULL;
}
list_out = PyList_New(TupleSize);
for(i=0;i<TupleSize;i++)
PyList_SET_ITEM(list_out, i, PyInt_FromLong(nums[i]));
free(nums);
return (PyObject *)list_out;
}
static PyMethodDef toolbox_methods[] = {
{ "hypot", (PyCFunction)hypot_tb, METH_VARARGS,
"Add docs here\n"},
// NULL terminate Python looking at the object
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC init_toolbox(void) {
Py_InitModule3("_toolbox", toolbox_methods,
"toolbox module");
}
In python then it is:
>>> import _toolbox
>>> _toolbox.hypot(*range(4, 10))
[4, 5, 6, 7, 8, 9]
I had used something like this earlier. It could be a bad code as I am not an experienced C coder, but it worked for me. The idea is, *args is just a Python tuple, and you can do anything that you could do with a Python tuple. You can check http://docs.python.org/c-api/tuple.html .
int
ParseArguments(unsigned long arr[],Py_ssize_t size, PyObject *args) {
/* Get arbitrary number of positive numbers from Py_Tuple */
Py_ssize_t i;
PyObject *temp_p, *temp_p2;
for (i=0;i<size;i++) {
temp_p = PyTuple_GetItem(args,i);
if(temp_p == NULL) {return NULL;}
/* Check if temp_p is numeric */
if (PyNumber_Check(temp_p) != 1) {
PyErr_SetString(PyExc_TypeError,"Non-numeric argument.");
return NULL;
}
/* Convert number to python long and than C unsigned long */
temp_p2 = PyNumber_Long(temp_p);
arr[i] = PyLong_AsUnsignedLong(temp_p2);
Py_DECREF(temp_p2);
if (arr[i] == 0) {
PyErr_SetString(PyExc_ValueError,"Zero doesn't allowed as argument.");
return NULL;
}
if (PyErr_Occurred()) {return NULL; }
}
return 1;
}
I was calling this function like this:
static PyObject *
function_name_was_here(PyObject *self, PyObject *args)
{
Py_ssize_t TupleSize = PyTuple_Size(args);
Py_ssize_t i;
struct bigcouples *temp = malloc(sizeof(struct bigcouples));
unsigned long current;
if(!TupleSize) {
if(!PyErr_Occurred())
PyErr_SetString(PyExc_TypeError,"You must supply at least one argument.");
free(temp);
return NULL;
}
unsigned long *nums = malloc(TupleSize * sizeof(unsigned long));
if(!ParseArguments(nums, TupleSize, args)){
/* Make a cleanup and than return null*/
return null;
}