bpo-38302: __pow__/__rpow__ now called when __ipow__ returns NotImplemented (#16459)

This commit is contained in:
Alex 2021-02-26 21:58:39 +02:00 committed by GitHub
parent 5e48e83688
commit cc02b4f2e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 21 deletions

View File

@ -276,6 +276,9 @@ Other Language Changes
the :meth:`~object.__int__` method but do not have the the :meth:`~object.__int__` method but do not have the
:meth:`~object.__index__` method). :meth:`~object.__index__` method).
(Contributed by Serhiy Storchaka in :issue:`37999`.) (Contributed by Serhiy Storchaka in :issue:`37999`.)
* If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator will
correctly fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected.
(Contributed by Alex Shkop in :issue:`38302`.)
* Assignment expressions can now be used unparenthesized within set literals * Assignment expressions can now be used unparenthesized within set literals
and set comprehensions, as well as in sequence indexes (but not slices). and set comprehensions, as well as in sequence indexes (but not slices).

View File

@ -3903,6 +3903,48 @@ order (MRO) for bases """
a = C() a = C()
a **= 2 a **= 2
def test_ipow_returns_not_implemented(self):
class A:
def __ipow__(self, other):
return NotImplemented
class B(A):
def __rpow__(self, other):
return 1
class C(A):
def __pow__(self, other):
return 2
a = A()
b = B()
c = C()
a **= b
self.assertEqual(a, 1)
c **= b
self.assertEqual(c, 2)
def test_no_ipow(self):
class B:
def __rpow__(self, other):
return 1
a = object()
b = B()
a **= b
self.assertEqual(a, 1)
def test_ipow_exception_text(self):
x = None
with self.assertRaises(TypeError) as cm:
x **= 2
self.assertIn('unsupported operand type(s) for **=', str(cm.exception))
with self.assertRaises(TypeError) as cm:
y = x ** 2
self.assertIn('unsupported operand type(s) for **', str(cm.exception))
def test_mutable_bases(self): def test_mutable_bases(self):
# Testing mutable bases... # Testing mutable bases...

View File

@ -0,0 +1 @@
If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator will correctly fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected.

View File

@ -882,10 +882,8 @@ static PyObject *
ternary_op(PyObject *v, ternary_op(PyObject *v,
PyObject *w, PyObject *w,
PyObject *z, PyObject *z,
const int op_slot const int op_slot,
#ifndef NDEBUG const char *op_name
, const char *op_name
#endif
) )
{ {
PyNumberMethods *mv = Py_TYPE(v)->tp_as_number; PyNumberMethods *mv = Py_TYPE(v)->tp_as_number;
@ -955,16 +953,18 @@ ternary_op(PyObject *v,
if (z == Py_None) { if (z == Py_None) {
PyErr_Format( PyErr_Format(
PyExc_TypeError, PyExc_TypeError,
"unsupported operand type(s) for ** or pow(): " "unsupported operand type(s) for %.100s: "
"'%.100s' and '%.100s'", "'%.100s' and '%.100s'",
op_name,
Py_TYPE(v)->tp_name, Py_TYPE(v)->tp_name,
Py_TYPE(w)->tp_name); Py_TYPE(w)->tp_name);
} }
else { else {
PyErr_Format( PyErr_Format(
PyExc_TypeError, PyExc_TypeError,
"unsupported operand type(s) for pow(): " "unsupported operand type(s) for %.100s: "
"'%.100s', '%.100s', '%.100s'", "'%.100s', '%.100s', '%.100s'",
op_name,
Py_TYPE(v)->tp_name, Py_TYPE(v)->tp_name,
Py_TYPE(w)->tp_name, Py_TYPE(w)->tp_name,
Py_TYPE(z)->tp_name); Py_TYPE(z)->tp_name);
@ -972,13 +972,6 @@ ternary_op(PyObject *v,
return NULL; return NULL;
} }
#ifdef NDEBUG
# define TERNARY_OP(v, w, z, op_slot, op_name) ternary_op(v, w, z, op_slot)
#else
# define TERNARY_OP(v, w, z, op_slot, op_name) ternary_op(v, w, z, op_slot, op_name)
#endif
#define BINARY_FUNC(func, op, op_name) \ #define BINARY_FUNC(func, op, op_name) \
PyObject * \ PyObject * \
func(PyObject *v, PyObject *w) { \ func(PyObject *v, PyObject *w) { \
@ -1077,7 +1070,7 @@ PyNumber_Remainder(PyObject *v, PyObject *w)
PyObject * PyObject *
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z) PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
{ {
return TERNARY_OP(v, w, z, NB_SLOT(nb_power), "** or pow()"); return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()");
} }
/* Binary in-place operators */ /* Binary in-place operators */
@ -1140,6 +1133,24 @@ binary_iop(PyObject *v, PyObject *w, const int iop_slot, const int op_slot,
return result; return result;
} }
static PyObject *
ternary_iop(PyObject *v, PyObject *w, PyObject *z, const int iop_slot, const int op_slot,
const char *op_name)
{
PyNumberMethods *mv = Py_TYPE(v)->tp_as_number;
if (mv != NULL) {
ternaryfunc slot = NB_TERNOP(mv, iop_slot);
if (slot) {
PyObject *x = (slot)(v, w, z);
if (x != Py_NotImplemented) {
return x;
}
Py_DECREF(x);
}
}
return ternary_op(v, w, z, op_slot, op_name);
}
#define INPLACE_BINOP(func, iop, op, op_name) \ #define INPLACE_BINOP(func, iop, op, op_name) \
PyObject * \ PyObject * \
func(PyObject *v, PyObject *w) { \ func(PyObject *v, PyObject *w) { \
@ -1237,13 +1248,8 @@ PyNumber_InPlaceRemainder(PyObject *v, PyObject *w)
PyObject * PyObject *
PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z) PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z)
{ {
if (Py_TYPE(v)->tp_as_number && return ternary_iop(v, w, z, NB_SLOT(nb_inplace_power),
Py_TYPE(v)->tp_as_number->nb_inplace_power != NULL) { NB_SLOT(nb_power), "**=");
return TERNARY_OP(v, w, z, NB_SLOT(nb_inplace_power), "**=");
}
else {
return TERNARY_OP(v, w, z, NB_SLOT(nb_power), "**=");
}
} }