https://github.com/python/cpython/commit/62ff86fa55c903a8362a64ecc363d8443aff2d07
commit: 62ff86fa55c903a8362a64ecc363d8443aff2d07
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
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 -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]