]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-38302: __pow__/__rpow__ now called when __ipow__ returns NotImplemented (#16459)
authorAlex <a.v.shkop@gmail.com>
Fri, 26 Feb 2021 19:58:39 +0000 (21:58 +0200)
committerGitHub <noreply@github.com>
Fri, 26 Feb 2021 19:58:39 +0000 (11:58 -0800)
Doc/whatsnew/3.10.rst
Lib/test/test_descr.py
Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst [new file with mode: 0644]
Objects/abstract.c

index d353f33c718019a19a5936ba51eb648175035859..310554eabe6706e2ada0e7300da7362d7f78c28d 100644 (file)
@@ -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).
index f0048f42f882b0d3c6de2d3c0ae13b8e8156cc5a..8c75ec304f7804c9c6821e59bc83a2d99cf1784e 100644 (file)
@@ -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 (file)
index 0000000..e9462f1
--- /dev/null
@@ -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
index c93309b352774cb44a212c88acb82242ef616ff2..4cd59100ddc567b33903bf8f419f340d9615d022 100644 (file)
@@ -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), "**=");
 }