https://github.com/python/cpython/commit/62ff86fa55c903a8362a64ecc363d8443aff2d07 commit: 62ff86fa55c903a8362a64ecc363d8443aff2d07 branch: main author: Serhiy Storchaka <storch...@gmail.com> committer: serhiy-storchaka <storch...@gmail.com> date: 2025-04-16T18:32:41+03:00 summary:
gh-130104: Call __rpow__ in ternary pow() if necessary (GH-130251) Previously it was only called in binary pow() and the binary power operator. files: A Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst M Doc/reference/datamodel.rst M Doc/whatsnew/3.14.rst M Lib/_pydecimal.py M Lib/fractions.py M Lib/test/test_capi/test_number.py M Lib/test/test_decimal.py M Lib/test/test_descr.py M Lib/test/test_fractions.py M Objects/typeobject.c diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 66b836eaf0008a..f1b7d33655c591 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -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:: diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 0e30500fc9b997..c50d1669fef84c 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -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 diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 38dc7b70e0f6f0..d666c4133c3c25 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -2440,12 +2440,12 @@ def __pow__(self, other, modulo=None, context=None): 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""" diff --git a/Lib/fractions.py b/Lib/fractions.py index f0cbc8c2e6c012..fa722589fb4f67 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -905,8 +905,10 @@ def __pow__(a, b, modulo=None): 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 diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 8e7070307d670f..bdd8868529f632 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -237,9 +237,8 @@ def __rpow__(*args): 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): diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index fb14b80f7a8a2b..92dafc56dc2d0b 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4493,12 +4493,10 @@ def test_implicit_context(self): 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)) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index aa453e438facd5..749f27bbd3a662 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3515,6 +3515,10 @@ def __rpow__(self, other, mod=None): 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() diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 98dccbec9566ac..84faa63606439e 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1707,6 +1707,12 @@ def test_three_argument_pow(self): 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 index 00000000000000..9539ad3d79d600 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst @@ -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. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 3a7fad4681b2a1..63e153e6715c17 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -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; } _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com