From cc02b4f2e810ab524d845daa18bc94df5b092dd8 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 26 Feb 2021 21:58:39 +0200 Subject: [PATCH] bpo-38302: __pow__/__rpow__ now called when __ipow__ returns NotImplemented (#16459) --- Doc/whatsnew/3.10.rst | 3 ++ Lib/test/test_descr.py | 42 ++++++++++++++++ .../2019-09-28-12-23-23.bpo-38302.hsCNgX.rst | 1 + Objects/abstract.c | 48 +++++++++++-------- 4 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index d353f33c718..310554eabe6 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -276,6 +276,9 @@ Other Language Changes the :meth:`~object.__int__` method but do not have the :meth:`~object.__index__` method). (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 and set comprehensions, as well as in sequence indexes (but not slices). diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index f0048f42f88..8c75ec304f7 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3903,6 +3903,48 @@ order (MRO) for bases """ a = C() 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): # Testing mutable bases... diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst b/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst new file mode 100644 index 00000000000..e9462f1facd --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst @@ -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. \ No newline at end of file diff --git a/Objects/abstract.c b/Objects/abstract.c index c93309b3527..4cd59100ddc 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -882,10 +882,8 @@ static PyObject * ternary_op(PyObject *v, PyObject *w, PyObject *z, - const int op_slot -#ifndef NDEBUG - , const char *op_name -#endif + const int op_slot, + const char *op_name ) { PyNumberMethods *mv = Py_TYPE(v)->tp_as_number; @@ -955,16 +953,18 @@ ternary_op(PyObject *v, if (z == Py_None) { PyErr_Format( PyExc_TypeError, - "unsupported operand type(s) for ** or pow(): " + "unsupported operand type(s) for %.100s: " "'%.100s' and '%.100s'", + op_name, Py_TYPE(v)->tp_name, Py_TYPE(w)->tp_name); } else { PyErr_Format( PyExc_TypeError, - "unsupported operand type(s) for pow(): " + "unsupported operand type(s) for %.100s: " "'%.100s', '%.100s', '%.100s'", + op_name, Py_TYPE(v)->tp_name, Py_TYPE(w)->tp_name, Py_TYPE(z)->tp_name); @@ -972,13 +972,6 @@ ternary_op(PyObject *v, 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) \ PyObject * \ func(PyObject *v, PyObject *w) { \ @@ -1077,7 +1070,7 @@ PyNumber_Remainder(PyObject *v, PyObject *w) PyObject * 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 */ @@ -1140,6 +1133,24 @@ binary_iop(PyObject *v, PyObject *w, const int iop_slot, const int op_slot, 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) \ PyObject * \ func(PyObject *v, PyObject *w) { \ @@ -1237,13 +1248,8 @@ PyNumber_InPlaceRemainder(PyObject *v, PyObject *w) PyObject * PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z) { - if (Py_TYPE(v)->tp_as_number && - Py_TYPE(v)->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), "**="); - } + return ternary_iop(v, w, z, NB_SLOT(nb_inplace_power), + NB_SLOT(nb_power), "**="); }