2

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?

4
  • "found out that neither Python float, nor int, nor str implement these methods": because those types are immutable. You cannot increment/change them and the "+=" operator is a "fake". a += 1 is the same thing as a = a + 1 (well, disassembly shows INPLACE_ADD but that's not really inplace and __iadd__ doesn't exist) Commented Jun 21, 2020 at 21:22
  • in the case the object doesn't provide __iadd__ you could fall back to addition then return the result Commented Jun 21, 2020 at 21:28
  • The operator module has an __iadd__ function that should do the right thing. Just call it. Commented Jun 21, 2020 at 21:32
  • I see. Would there be any detriment in mutating, say, a PyFloatObject in C++, e.g. a->ob_fval += 4.2;? Commented Jun 21, 2020 at 21:34

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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.

7 Comments

You still need to assign back to a in the iadd cases - it's possible, though unusual, for the "in-place" methods to return a different object.
I did the opposite of your suggestion exactly for the same reason as yours. For my application, if iadd returned a different object, I want my original, supposedly incremented, object rather than the returned one.
You're also missing a dispatch to the right-hand-side's method, as well as the logic for which side's nb_add to try first.
"if iadd returned a different object, I want my original, supposedly incremented, object rather than the returned one" - that's kind of weird and not how Python's += works.
"that's kind of weird": This is a part of a histogramming code. If iadd returned a sentinel signaling whether the addition was successful, which could fail if the two types were not compatible, I would lose my bin object.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.