Search code examples
pythonc++overloadingcpythonpython-c-api

CPython 'overloaded' functions


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?


Solution

  • 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.