__ipow__ 当左侧对象 returns 未实现时引发 TypeError

__ipow__ raising TypeError when left-hand-side object returns NotImplemented

如果我有两个对象 AB,我可以 return NotImplemented 用于 A__iadd__ 方法并且有B 使用它的 __radd__ 方法修改 A

>>> class A():
...     def __init__(self, val):
...         self.val = val
...     def __iadd__(self, other):
...         return NotImplemented
...     def __ipow__(self, other):
...         return NotImplemented 
... 
>>> class B():
...     def __init__(self, val):
...         self.val = val
...     def __radd__(self, other):
...         return A(other.val + self.val)
...     def __rpow__(self, other):
...         return A(other.val ** self.val) 
... 
>>> a = A(2)
>>> b = B(2)
>>> a += b
>>> a.val
4

这似乎适用于所有就地运算符,但 __ipow__ 除外,其中引发了 TypeError

>>> a **= b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for ** or pow(): 'A' and 'B'

为什么这里的行为不同?失败是因为 pow() requires numeric data 吗?最好的解决方法是什么?

Python 3.10+

此错误已在 Python 3.10

中修复

Due to a bug in the dispatching mechanism for **=, a class that defines __ipow__() but returns NotImplemented would fail to fall back to x.__pow__(y) and y.__rpow__(x). This bug is fixed in Python 3.10.

代码段现在按预期运行:

>>> a = A(2)
>>> b = B(2)
>>> a **= b
>>> a.val
4

这看起来像是一个错误,因为二进制和三元运算的代码之间存在不一致(**= 由于与 3 参数 pow 共享代码而由三元运算逻辑处理) .二进制就地操作通过 binary_iop1,如果就地处理程序 returns NotImplemented:

,它具有回退到非就地例程的代码
static PyObject *
binary_iop1(PyObject *v, PyObject *w, const int iop_slot, const int op_slot)
{
    PyNumberMethods *mv = v->ob_type->tp_as_number;
    if (mv != NULL) {
        binaryfunc slot = NB_BINOP(mv, iop_slot);
        if (slot) {
            PyObject *x = (slot)(v, w);
            if (x != Py_NotImplemented) {
                return x;
            }
            Py_DECREF(x);
        }
    }
    return binary_op1(v, w, op_slot);
}

但由于 3 参数 pow 所需的代码差异,**= 无法通过该代码路径,因此它具有 its own ad-hoc handling:

PyObject *
PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z)
{
    if (v->ob_type->tp_as_number &&
        v->ob_type->tp_as_number->nb_inplace_power != NULL) {
        return ternary_op(v, w, z, NB_SLOT(nb_inplace_power), "**=");
    }
    else {
        return ternary_op(v, w, z, NB_SLOT(nb_power), "**=");
    }
}

此临时处理提交到就地或非就地端,如果就地处理程序无法处理,则不会回退。