4

I've looked everywhere, but I can't find an example of PyArg_ParseTupleAndKeywords() being used with a tuple — containing optional arguments — and keywords. The closest I've found is this question, but the answer isn't particularly helpful. Most examples seem to have the keywords as the optional arguments, but it seems like the tuple should be able to contain optional arguments as well.

Suppose I'm trying to parse the following parameters:

  • numpy array of doubles (mandatory)
  • numpy array of doubles (optional, no keyword)
  • optional keyword arguments:
    • k1 => numpy array of doubles
    • k2 => integer
    • k3 => double
    • k4 => Python class instance

It seems like I should be doing something like

static PyObject* pymod_func(PyObject* self, PyObject* args, PyObject* kwargs) {
  static char* keywords[] = {"k1", "k2", "k3", "k4", NULL};

  PyObject *arg1, *arg2, *k1, *k4
  PyObject *arr1, *arr2, *karr1;
  double *k3;
  int *k2;
  PyArg_ParseTupleAndKeywords(args, kwargs, "O!|O!OidO", keywords, &arg1, &PyArray_Type, &arg2, &PyArray_Type, &k1, &PyArray_Type, &k2, &k3, &k4);

  arr1 = PyArray_FROM_OTF(arg1, NPY_FLOAT64, NPY_ARRAY_INOUT_ARRAY);
  if (arr1 == NULL) return NULL;

  arr2 = PyArray_FROM_OTF(arg1, NPY_FLOAT64, NPY_ARRAY_INOUT_ARRAY);
  // no null check, because optional

  karr1 = PyArray_FROM_OTF(k1, NPY_FLOAT64, NPY_ARRAY_INOUT_ARRAY);
  // again, no null check, because this is optional

  // do things with k3, k2, and k4 also

  return NULL;
}

Other places I've looked, but without finding much help:

What is the appropriate way to use PyArg_ParseTupleAndKeywords()?

Community
  • 1
  • 1
Translunar
  • 3,739
  • 33
  • 55

2 Answers2

3

As of Python 3.3, you can use $ in the format string to indicate that the rest of the arguments are keyword-only, and as of Python 3.6, you can indicate a positional-only parameter by using an empty name in the keywords argument.

So, in a sufficiently high version of Python, you can use code like:

static char* keywords[] = {"", "", "k1", "k2", "k3", "k4", NULL};

// [...]

PyArg_ParseTupleAndKeywords(args, kwargs,
                            "O!|O!$O!idO", keywords,
                            &PyArray_Type, &arg1, &PyArray_Type, &arg2,
                            &PyArray_Type, &k1, &k2, &k3, &k4);
Rich
  • 7,348
  • 4
  • 34
  • 54
2

I think something similar to the below simplified solution should work for your scenario but it can get nasty if you have many optional args or more interesting arg types. I'm not sure if there is a better solution but I haven't been able to find one. Hopefully one day someone will post a cleaner solution.

You'll have to be clever to produce useful arg parsing error messages in more complex parsing scenarios.

static PyObject* nasty_func(PyObject* self, PyObject* args, PyObject* kwargs) {
  static char* keywords[] = {"one", "optional", "two", NULL};
  static char* keywords_alt[] = {"one", "two", NULL};

  int ok = 0;
  PyObject *result = NULL;

  int *one;
  char *two;
  int *optional;

  ok = PyArg_ParseTupleAndKeywords(args, kwargs, "iis", keywords, &one, &optional, &two);

  if (!ok) {
    PyErr_Clear();
    ok = PyArg_ParseTupleAndKeywords(args, kwargs, "is", keywords_alt, &one, &two);
    if (!ok) {
      PyErr_SetString(PyExc_TypeError, "Invalid args. allowed formats: 'one:i, two:s' or 'one:i, optional:i, two:s'");
      return NULL;
    }
  }

  // do stuff with your parsed variables

  return result;
}
brando
  • 598
  • 6
  • 11