在 C++ 中的 PyObject 上调用 `+=`

Posted

技术标签:

【中文标题】在 C++ 中的 PyObject 上调用 `+=`【英文标题】:Call `+=` on a PyObject in C++ 【发布时间】:2020-06-21 21:20:15 【问题描述】:

我正在用 C++ 编写一个 Python 模块。

在某个时候,我需要将任意类型的PyObject 添加到另一个。换句话说,就像a += b 在 Python 中做的一样。但我无法在 API 中找到执行此操作的函数。

我尝试了以下操作,

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;

但发现 Python floatintstr 都没有实现这些方法。

API 中是否有应用通用+= 的函数?如果没有,我怎么写?

【问题讨论】:

“发现 Python 的 float、int 和 str 都没有实现这些方法”:因为这些类型是不可变的。您不能增加/更改它们,并且“+=”运算符是“假的”。 a += 1a = a + 1 是一回事(好吧,反汇编显示 INPLACE_ADD 但这并不是真的到位,__iadd__ 不存在) 如果对象不提供__iadd__,您可以退回到加法然后返回结果 operator 模块有一个 __iadd__ 函数,它应该做正确的事情。就叫吧。 我明白了。在 C++ 中改变 PyFloatObject 会不会有任何损害,例如a->ob_fval += 4.2;? 【参考方案1】:

在某个时刻,我需要将任意类型的 PyObject 添加到另一个中。换句话说,执行与 Python 中的 a += b 相同的操作。但我无法在 API 中找到执行此操作的函数。

你找不到的函数是PyNumber_InPlaceAdd

x += y

在 Python 级别相当于

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

在 C 级。 (如果需要,您也可以同时保留 sumx,这对于异常处理很有用。)

PyNumber_InPlaceAdd 处理+= 的完整调度逻辑,包括__iadd__、双方的__add__ 方法(按正确顺序)、NotImplemented 哨兵和nb_inplace_addnb_addsq_inplace_concatsq_concat C 级挂钩。

【讨论】:

【参考方案2】:

我最终做了这样的事情:

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));

如果__iadd__ 不可用,这将依赖__add__,正如Jean-François Fabre 建议的那样。

【讨论】:

iadd 情况下,您仍然需要分配回a - 尽管不寻常,但“就地”方法可能会返回不同的对象。 出于与您相同的原因,我与您的建议完全相反。对于我的应用程序,如果 iadd 返回了一个不同的对象,我想要我的原始对象,据说是递增的,而不是返回的对象。 您还缺少对右侧方法的调度,以及首先尝试哪一侧的nb_add 的逻辑。 “如果 iadd 返回一个不同的对象,我想要我原来的,据说是递增的,而不是返回的对象”——这有点奇怪,而不是 Python 的 += 的工作方式。 “这有点奇怪”:这是直方图代码的一部分。如果iadd 返回一个标记是否添加成功,如果这两种类型不兼容可能会失败,我会丢失我的 bin 对象。

以上是关于在 C++ 中的 PyObject 上调用 `+=`的主要内容,如果未能解决你的问题,请参考以下文章

PyObject_CallMethod 在调用 python 方法时有时会出现段错误

在 C++ 程序中调用 Python 函数

如何在python中控制c++类对象?

如何将 C++ 对象转换为 PyObject?

PyObject_CallObject 在 bytearray 方法上失败

从 C++ 函数 Cython 返回包含 PyObject 的复杂对象