Call `+=` on a PyObject in C++

Viewed 142

I'm writing a Python module in C++.

At a certain point, I need to add a PyObject of an arbitrary type to another one. In other words, do the same as a += b would do in Python. But I haven't been able to find a function in the API that does that.

I tried to do the following,

PyObject* increment(PyObject* a, PyObject* b) {
  const auto tp = Py_TYPE(a);
  [&]{
    if (const auto nb = tp->tp_as_number)
      if (auto iadd = nb->nb_inplace_add)
        return iadd;
    if (const auto sq = tp->tp_as_sequence)
      if (auto iadd = sq->sq_inplace_concat)
        return iadd;
    throw error(PyExc_TypeError,
      "type "s+(tp->tp_name)+" does not provide __iadd__");
  }()(a,b);
  return a;
}

but found out that neither Python float, nor int, nor str implement these methods.

Is there a function in the API that applies a generic +=? If not, how would I write one?

2 Answers

At a certain point, I need to add a PyObject of an arbitrary type to another one. In other words, do the same as a += b would do in Python. But I haven't been able to find a function in the API that does that.

The function you weren't able to find is PyNumber_InPlaceAdd.

x += y

at Python level is equivalent to

sum = PyNumber_InPlaceAdd(x, y);
Py_DECREF(x);
x = sum;

at C level. (You can also keep both sum and x if you want, which can be useful for exception handling.)

PyNumber_InPlaceAdd handles the full dispatch logic of +=, including __iadd__, both sides' __add__ methods (in the correct order), the NotImplemented sentinel, and the nb_inplace_add, nb_add, sq_inplace_concat, and sq_concat C-level hooks.

I ended up doing something like this:

static PyObject* increment(PyObject*& a, PyObject* b) {
  const auto tp = Py_TYPE(a);
  if (const auto nb = tp->tp_as_number) {
    if (auto iadd = nb->nb_inplace_add)
      return iadd(a,args), a;
    if (auto add = nb->nb_add)
      return a = add(a,b);
  }
  if (const auto sq = tp->tp_as_sequence) {
    if (auto iadd = sq->sq_inplace_concat)
      return iadd(a,b), a;
    if (auto add = sq->sq_concat)
      return a = add(a,b);
  }
  throw error(PyExc_TypeError,
    "cannot increment "s+(tp->tp_name)+" by "+(Py_TYPE(b)->tp_name));
}

That is falling back on __add__ if __iadd__ is not available, as Jean-François Fabre suggested.

Related