diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index c4a79b6a1e98fa..5ea65fcf0126e7 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -285,6 +285,10 @@ Other Language Changes ``__globals__['__builtins__']``. (Contributed by Mark Shannon in :issue:`42990`.) +* `**=` will try ``__pow__``/``__rpow__`` when ``__ipow__`` returns + :class:`NotImplemented` as defined by the language specification. + (Contributed by Brett Cannon in :issue:`38302`.) + New Modules =========== diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index f0048f42f882b0..bbeaeb0b2824ed 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3903,6 +3903,25 @@ def __ipow__(self, other): a = C() a **= 2 + def test_pow_fallback_for_ipow(self): + # If __ipow__ returns NotImplemented, call __pow__. + # https://bugs.python.org/issue38302 + class C: + def __init__(self, val): + self._val = val + + def __ipow__(self, other): + return NotImplemented + + def __pow__(self, other): + return self._val ** other + + base = 3 + exponent = 5 + a = C(base) + a **= exponent + self.assertEqual(a, base ** exponent) + def test_mutable_bases(self): # Testing mutable bases... diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-02-19-15-17-50.bpo-38302.Je4zmv.rst b/Misc/NEWS.d/next/Core and Builtins/2021-02-19-15-17-50.bpo-38302.Je4zmv.rst new file mode 100644 index 00000000000000..e2ed938ffbb081 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-02-19-15-17-50.bpo-38302.Je4zmv.rst @@ -0,0 +1 @@ +When __ipow__ returns NotImplemented, fall back to trying __pow__/__rpow__. diff --git a/Objects/abstract.c b/Objects/abstract.c index c93309b352774c..e5f08616338562 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -845,6 +845,31 @@ binop_type_error(PyObject *v, PyObject *w, const char *op_name) return NULL; } +static PyObject * +ternaryop_type_error(PyObject *v, PyObject *w, PyObject *z, const char *op_name) +{ + if (z == Py_None) { + PyErr_Format( + PyExc_TypeError, + "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 %.100s: " + "'%.100s', '%.100s', '%.100s'", + op_name, + Py_TYPE(v)->tp_name, + Py_TYPE(w)->tp_name, + Py_TYPE(z)->tp_name); + } + return NULL; +} + static PyObject * binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name) { @@ -952,24 +977,7 @@ ternary_op(PyObject *v, } } - if (z == Py_None) { - PyErr_Format( - PyExc_TypeError, - "unsupported operand type(s) for ** or pow(): " - "'%.100s' and '%.100s'", - Py_TYPE(v)->tp_name, - Py_TYPE(w)->tp_name); - } - else { - PyErr_Format( - PyExc_TypeError, - "unsupported operand type(s) for pow(): " - "'%.100s', '%.100s', '%.100s'", - Py_TYPE(v)->tp_name, - Py_TYPE(w)->tp_name, - Py_TYPE(z)->tp_name); - } - return NULL; + Py_RETURN_NOTIMPLEMENTED; } #ifdef NDEBUG @@ -1077,7 +1085,12 @@ 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()"); + PyObject *result = TERNARY_OP(v, w, z, NB_SLOT(nb_power), "** or pow()"); + if (result == Py_NotImplemented) { + Py_DECREF(result); + return ternaryop_type_error(v, w, z, "** or pow()"); + } + return result; } /* Binary in-place operators */ @@ -1237,13 +1250,16 @@ 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), "**="); + PyObject *result = TERNARY_OP(v, w, z, NB_SLOT(nb_inplace_power), "**="); + if (result == Py_NotImplemented) { + Py_DECREF(result); + result = TERNARY_OP(v, w, z, NB_SLOT(nb_power), "**="); + if (result == Py_NotImplemented) { + Py_DECREF(result); + return ternaryop_type_error(v, w, z, "**="); + } } + return result; }