0

I am trying to overload a python extension function that would take either a object or a string.

typedef struct
{
  PyObject_HEAD
} CustomObject;

PyObject* customFunction(CustomObject* self, PyObject* args);

PyMethodDef methods[] =
{
 {"customFunction", (PyCFunction) customFunction, METH_VARAGS, "A custom function"},
 {NULL}
}

PyTypeObject TypeObj =
{
  PyVarObject_HEAD_INIT(NULL, 0)
  .tp_name = "customModule.CustomObject",
  .tp_doc = "Custom Object",
  .tp_basicsize = sizeof(CustomObject),
  .tp_itemsize = 0,
  .tp_flags = Py_TPFLAGS_DEFAULT,
  .tp_methods = methods,
}

// Area of problem
PyObject* customFunction(CustomObject* self, PyObject* args)
{
  const char* string;
  PyObject* object;
  if (PyArg_ParseTuple(args, "O!", &TypeObj, &object)) // TypeObj is the PyTypeObject fpr CustomObject
  {
    std::cout << "Object function\n"
    // Do whatever and return PyObject*
  }
  else if (PyArg_ParseTuple(args, "s", &string))
  {
    std::cout << "String function\n"
    // Do whatever and return PyObject*
  }
  return PyLong_FromLong(0); // In case nothing above works
}

In python I have a try except for the function and I get this error Error: <built-in method customFunction of CustomModule.CustomObject object at 0xmemoryadress> returned a result with an error set

Here are the Python docs for this PyArg_ParseTuple:

int PyArg_ParseTuple(PyObject *args, const char *format, ...)

Parse the parameters of a function that takes only positional parameters into local variables. Returns true on success; on failure, it returns false and raises the appropriate exception

I am guessing that PyArg_ParseTuple is setting an error, which is causing the entire function not to work (I do have customFunction in my method table for the module, I am just omitting that code). If I have the following Python:

import CustomModule

try:
  CustomModule.customFunction("foo")
except Exception as e:
  print("Error:", e)

String function does get outputted, so the code in the string if statement does work, but I assume the error occurs because PyArg_ParseTuple for the object failed, so it returns an error (not 100% sure if this is correct).

Is there a way I can prevent PyArg_ParseTuple() from raising an error, is there another function, or is there a better way to 'overload' my custom functions?

A student
  • 170
  • 11
  • "on failure, it returns false and raises the appropriate exception" - you can do that in Python? Today I learned. Raise an exception *and* return a value. Interresting. – Jesper Juhl Nov 27 '19 at 19:29
  • Please provide a [mcve]. You haven't shown us how `TypeObj` is intantiated – AndyG Nov 27 '19 at 19:30
  • @JesperJuhl: As far as I understand, it's akin to using error codes. E.g., `PyErr_SetString` – AndyG Nov 27 '19 at 19:32
  • 1
    @JesperJuhl `PyArg_ParseTuple` is a C function. It returns a C 0 value if something goes wrong, and then sets the Python exception state. This isn't something you can do in Python - it's just how Python exceptions are implemented in C. – DavidW Nov 27 '19 at 19:39
  • @DavidW thank you for the explanation. – Jesper Juhl Nov 27 '19 at 19:45

1 Answers1

2

I'd probably just use PyArg_ParseTuple to get a generic unspecified object, and then handle the object types later with Py*_Check:

if (!PyArg_ParseTuple(args, "O", &object)) {
    return NULL;
}
if (PyObject_IsInstance(object, (PyObject*)&PyType)) { // or a more specific function if one exists
    std::cout << "Object function\n";
} else if (PyUnicode_Check(object)) {
    std::cout << "String function\n";
} else {
    // set an error, return NULL
}

The reason for this is that the Python "ask forgiveness, not permission" pattern of

try:
    something()
except SomeException:
    somethingElse()

doesn't translate very well into C, and involves quite a bit of code to handle the exceptions. If you really want to do it that way then you need to call PyErr_Clear before the second PyArg_ParseTuple, and ideally you should check it's the exception you think, and not something else entirely.

DavidW
  • 29,336
  • 6
  • 55
  • 86
  • How would I represent CustomObject as a PyObject* class? – A student Nov 27 '19 at 19:49
  • 1
    Ah - `CustomObject` is a `PyTypeObject`. You just need to cast it: `(PyObject*)&CustomObject`. A `PyTypeObject` is a variant of `PyObject` so that's fine. – DavidW Nov 27 '19 at 19:51
  • 1
    Careful - I missed a `&`. Make sure you copy the right edit! – DavidW Nov 27 '19 at 19:53
  • Final comment: since you're using C++ you might prefer a C++-style cast `reinterpret_cast(&CustomObject)`. It'll run exactly the same, but some people prefer it as being clearer. – DavidW Nov 27 '19 at 20:52