]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-130104: Call __rpow__ in ternary pow() if necessary (GH-130251)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 16 Apr 2025 15:32:41 +0000 (18:32 +0300)
committerGitHub <noreply@github.com>
Wed, 16 Apr 2025 15:32:41 +0000 (18:32 +0300)
Previously it was only called in binary pow() and the binary
power operator.

Doc/reference/datamodel.rst
Doc/whatsnew/3.14.rst
Lib/_pydecimal.py
Lib/fractions.py
Lib/test/test_capi/test_number.py
Lib/test/test_decimal.py
Lib/test/test_descr.py
Lib/test/test_fractions.py
Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst [new file with mode: 0644]
Objects/typeobject.c

index 66b836eaf0008aea26a1e618eecac3f4fab4fd81..f1b7d33655c591d51361e3611ff968f298eecf9d 100644 (file)
@@ -3319,7 +3319,7 @@ left undefined.
    :meth:`__divmod__` method should be the equivalent to using
    :meth:`__floordiv__` and :meth:`__mod__`; it should not be related to
    :meth:`__truediv__`.  Note that :meth:`__pow__` should be defined to accept
-   an optional third argument if the ternary version of the built-in :func:`pow`
+   an optional third argument if the three-argument version of the built-in :func:`pow`
    function is to be supported.
 
    If one of those methods does not support the operation with the supplied
@@ -3356,10 +3356,15 @@ left undefined.
    is called if ``type(x).__sub__(x, y)`` returns :data:`NotImplemented` or ``type(y)``
    is a subclass of ``type(x)``. [#]_
 
-   .. index:: pair: built-in function; pow
+   Note that :meth:`__rpow__` should be defined to accept an optional third
+   argument if the three-argument version of the built-in :func:`pow` function
+   is to be supported.
 
-   Note that ternary :func:`pow` will not try calling :meth:`__rpow__` (the
-   coercion rules would become too complicated).
+   .. versionchanged:: next
+
+      Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
+      Previously it was only called in two-argument :func:`!pow` and the binary
+      power operator.
 
    .. note::
 
index 0e30500fc9b99779c397720084f849ac6a0ecd62..c50d1669fef84c22cbc07560b181ccd1cf549d7d 100644 (file)
@@ -458,6 +458,11 @@ Other language changes
   The testbed can also be used to run the test suite of projects other than
   CPython itself. (Contributed by Russell Keith-Magee in :gh:`127592`.)
 
+* Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
+  Previously it was only called in two-argument :func:`!pow` and the binary
+  power operator.
+  (Contributed by Serhiy Storchaka in :gh:`130104`.)
+
 * Add a built-in implementation for HMAC (:rfc:`2104`) using formally verified
   code from the `HACL* <https://github.com/hacl-star/hacl-star/>`__ project.
   This implementation is used as a fallback when the OpenSSL implementation
index 38dc7b70e0f6f0ff74dba547cc79dfe15dcc2437..d666c4133c3c255e2881b62dcb1287a25abf44c6 100644 (file)
@@ -2440,12 +2440,12 @@ class Decimal(object):
 
         return ans
 
-    def __rpow__(self, other, context=None):
+    def __rpow__(self, other, modulo=None, context=None):
         """Swaps self/other and returns __pow__."""
         other = _convert_other(other)
         if other is NotImplemented:
             return other
-        return other.__pow__(self, context=context)
+        return other.__pow__(self, modulo, context=context)
 
     def normalize(self, context=None):
         """Normalize- strip trailing 0s, change anything equal to 0 to 0e0"""
index f0cbc8c2e6c012aa4a21257e575d780bf9492e94..fa722589fb4f67cb6026a2db6ed3a0ddd31dca43 100644 (file)
@@ -905,8 +905,10 @@ class Fraction(numbers.Rational):
         else:
             return NotImplemented
 
-    def __rpow__(b, a):
+    def __rpow__(b, a, modulo=None):
         """a ** b"""
+        if modulo is not None:
+            return NotImplemented
         if b._denominator == 1 and b._numerator >= 0:
             # If a is an int, keep it that way if possible.
             return a ** b._numerator
index 8e7070307d670f03f9298cc7755106aa71b783b2..bdd8868529f6328a2f8806b3bc550f0d3cda0811 100644 (file)
@@ -237,9 +237,8 @@ class CAPITest(unittest.TestCase):
         x = X()
         self.assertEqual(power(4, x), (x, 4))
         self.assertEqual(inplacepower(4, x), (x, 4))
-        # XXX: Three-arg power doesn't use __rpow__.
-        self.assertRaises(TypeError, power, 4, x, 5)
-        self.assertRaises(TypeError, inplacepower, 4, x, 5)
+        self.assertEqual(power(4, x, 5), (x, 4, 5))
+        self.assertEqual(inplacepower(4, x, 5), (x, 4, 5))
 
         class X:
             def __ipow__(*args):
index fb14b80f7a8a2bd9c07dae7757b137a4632bbe3f..92dafc56dc2d0bfa57311d6e26f929537aaf1c62 100644 (file)
@@ -4493,12 +4493,10 @@ class Coverage:
             self.assertIs(Decimal("NaN").fma(7, 1).is_nan(), True)
             # three arg power
             self.assertEqual(pow(Decimal(10), 2, 7), 2)
+            self.assertEqual(pow(10, Decimal(2), 7), 2)
             if self.decimal == C:
-                self.assertEqual(pow(10, Decimal(2), 7), 2)
                 self.assertEqual(pow(10, 2, Decimal(7)), 2)
             else:
-                # XXX: Three-arg power doesn't use __rpow__.
-                self.assertRaises(TypeError, pow, 10, Decimal(2), 7)
                 # XXX: There is no special method to dispatch on the
                 # third arg of three-arg power.
                 self.assertRaises(TypeError, pow, 10, 2, Decimal(7))
index aa453e438facd534d625088bd5c4b9c23cf72d59..749f27bbd3a662bc89bb105b3472180a5a5270cd 100644 (file)
@@ -3515,6 +3515,10 @@ class ClassPropertiesAndMethods(unittest.TestCase):
         self.assertEqual(repr(2 ** I(3)), "I(8)")
         self.assertEqual(repr(I(2) ** 3), "I(8)")
         self.assertEqual(repr(pow(I(2), I(3), I(5))), "I(3)")
+        self.assertEqual(repr(pow(I(2), I(3), 5)), "I(3)")
+        self.assertEqual(repr(pow(I(2), 3, 5)), "I(3)")
+        self.assertEqual(repr(pow(2, I(3), 5)), "I(3)")
+        self.assertEqual(repr(pow(2, 3, I(5))), "3")
         class S(str):
             def __eq__(self, other):
                 return self.lower() == other.lower()
index 98dccbec9566acced6231a3e0a84b7d51e75cf68..84faa63606439e16123570db0dfb7581e38797e4 100644 (file)
@@ -1707,6 +1707,12 @@ class FractionTest(unittest.TestCase):
         self.assertRaisesMessage(TypeError,
                                  message % ("Fraction", "int", "int"),
                                  pow, F(3), 4, 5)
+        self.assertRaisesMessage(TypeError,
+                                 message % ("int", "Fraction", "int"),
+                                 pow, 3, F(4), 5)
+        self.assertRaisesMessage(TypeError,
+                                 message % ("int", "int", "Fraction"),
+                                 pow, 3, 4, F(5))
 
 
 if __name__ == '__main__':
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst
new file mode 100644 (file)
index 0000000..9539ad3
--- /dev/null
@@ -0,0 +1,4 @@
+Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if
+necessary.
+Previously it was only called in two-argument :func:`!pow` and the binary
+power operator.
index 3a7fad4681b2a10468bb93fa87a89ea0a1591345..63e153e6715c17f912f8eb853dedd2694d45c007 100644 (file)
@@ -9992,13 +9992,46 @@ slot_nb_power(PyObject *self, PyObject *other, PyObject *modulus)
 {
     if (modulus == Py_None)
         return slot_nb_power_binary(self, other);
-    /* Three-arg power doesn't use __rpow__.  But ternary_op
-       can call this when the second argument's type uses
-       slot_nb_power, so check before calling self.__pow__. */
+
+    /* The following code is a copy of SLOT1BINFULL, but for three arguments. */
+    PyObject* stack[3];
+    PyThreadState *tstate = _PyThreadState_GET();
+    int do_other = !Py_IS_TYPE(self, Py_TYPE(other)) &&
+        Py_TYPE(other)->tp_as_number != NULL &&
+        Py_TYPE(other)->tp_as_number->nb_power == slot_nb_power;
     if (Py_TYPE(self)->tp_as_number != NULL &&
         Py_TYPE(self)->tp_as_number->nb_power == slot_nb_power) {
-        PyObject* stack[3] = {self, other, modulus};
-        return vectorcall_method(&_Py_ID(__pow__), stack, 3);
+        PyObject *r;
+        if (do_other && PyType_IsSubtype(Py_TYPE(other), Py_TYPE(self))) {
+            int ok = method_is_overloaded(self, other, &_Py_ID(__rpow__));
+            if (ok < 0) {
+                return NULL;
+            }
+            if (ok) {
+                stack[0] = other;
+                stack[1] = self;
+                stack[2] = modulus;
+                r = vectorcall_maybe(tstate, &_Py_ID(__rpow__), stack, 3);
+                if (r != Py_NotImplemented)
+                    return r;
+                Py_DECREF(r);
+                do_other = 0;
+            }
+        }
+        stack[0] = self;
+        stack[1] = other;
+        stack[2] = modulus;
+        r = vectorcall_maybe(tstate, &_Py_ID(__pow__), stack, 3);
+        if (r != Py_NotImplemented ||
+            Py_IS_TYPE(other, Py_TYPE(self)))
+            return r;
+        Py_DECREF(r);
+    }
+    if (do_other) {
+        stack[0] = other;
+        stack[1] = self;
+        stack[2] = modulus;
+        return vectorcall_maybe(tstate, &_Py_ID(__rpow__), stack, 3);
     }
     Py_RETURN_NOTIMPLEMENTED;
 }