__ipow__ вызывает TypeError, когда левый объект возвращает NotImplemented - PullRequest
3 голосов
/ 28 сентября 2019

Если у меня есть два объекта A и B, я могу вернуть NotImplemented для A '__iadd__ метода и иметь B изменить A, используя его __radd__ метод.

>>> 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() требует числовые данные ?Какой лучший обходной путь для этого?

1 Ответ

4 голосов
/ 28 сентября 2019

Это похоже на ошибку из-за несоответствия между кодом для двоичных и троичных операций (при **= обрабатывается логикой троичной операции из-за совместного использования кода с 3 аргументами pow).Двоичные операции на месте проходят через binary_iop1, в котором есть код для возврата к подпрограмме «не на месте», если обработчик на месте возвращает 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, **= не может пройти этот путь кода, поэтому он имеет свою собственную специальную обработку :

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), "**=");
    }
}

Эта специальная обработка фиксирует либо на месте, либо на месте, без отступления, если обработчик на месте не может его обработать.

...