Mailing List Archive

bpo-38302: __pow__/__rpow__ now called when __ipow__ returns NotImplemented (#16459)
https://github.com/python/cpython/commit/cc02b4f2e810ab524d845daa18bc94df5b092dd8
commit: cc02b4f2e810ab524d845daa18bc94df5b092dd8
branch: master
author: Alex <a.v.shkop@gmail.com>
committer: brettcannon <brett@python.org>
date: 2021-02-26T11:58:39-08:00
summary:

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

files:
A Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst
M Doc/whatsnew/3.10.rst
M Lib/test/test_descr.py
M Objects/abstract.c

diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index d353f33c71801..310554eabe670 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 f0048f42f882b..8c75ec304f780 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -3903,6 +3903,48 @@ def __ipow__(self, other):
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 0000000000000..e9462f1facd8f
--- /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 c93309b352774..4cd59100ddc56 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), "**=");
}



_______________________________________________
Python-checkins mailing list
Python-checkins@python.org
https://mail.python.org/mailman/listinfo/python-checkins