gh-130104: Call __rpow__ in ternary pow() if necessary (GH-130251)

Previously it was only called in binary pow() and the binary
power operator.
This commit is contained in:
Serhiy Storchaka 2025-04-16 18:32:41 +03:00 committed by GitHub
parent 72da4a4458
commit 62ff86fa55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 74 additions and 18 deletions

View file

@ -3319,7 +3319,7 @@ left undefined.
:meth:`__divmod__` method should be the equivalent to using :meth:`__divmod__` method should be the equivalent to using
:meth:`__floordiv__` and :meth:`__mod__`; it should not be related to :meth:`__floordiv__` and :meth:`__mod__`; it should not be related to
:meth:`__truediv__`. Note that :meth:`__pow__` should be defined to accept :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. function is to be supported.
If one of those methods does not support the operation with the supplied 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 called if ``type(x).__sub__(x, y)`` returns :data:`NotImplemented` or ``type(y)``
is a subclass of ``type(x)``. [#]_ 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 .. versionchanged:: next
coercion rules would become too complicated).
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:: .. note::

View file

@ -458,6 +458,11 @@ Other language changes
The testbed can also be used to run the test suite of projects other than 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`.) 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 * Add a built-in implementation for HMAC (:rfc:`2104`) using formally verified
code from the `HACL* <https://github.com/hacl-star/hacl-star/>`__ project. code from the `HACL* <https://github.com/hacl-star/hacl-star/>`__ project.
This implementation is used as a fallback when the OpenSSL implementation This implementation is used as a fallback when the OpenSSL implementation

View file

@ -2440,12 +2440,12 @@ class Decimal(object):
return ans return ans
def __rpow__(self, other, context=None): def __rpow__(self, other, modulo=None, context=None):
"""Swaps self/other and returns __pow__.""" """Swaps self/other and returns __pow__."""
other = _convert_other(other) other = _convert_other(other)
if other is NotImplemented: if other is NotImplemented:
return other return other
return other.__pow__(self, context=context) return other.__pow__(self, modulo, context=context)
def normalize(self, context=None): def normalize(self, context=None):
"""Normalize- strip trailing 0s, change anything equal to 0 to 0e0""" """Normalize- strip trailing 0s, change anything equal to 0 to 0e0"""

View file

@ -905,8 +905,10 @@ class Fraction(numbers.Rational):
else: else:
return NotImplemented return NotImplemented
def __rpow__(b, a): def __rpow__(b, a, modulo=None):
"""a ** b""" """a ** b"""
if modulo is not None:
return NotImplemented
if b._denominator == 1 and b._numerator >= 0: if b._denominator == 1 and b._numerator >= 0:
# If a is an int, keep it that way if possible. # If a is an int, keep it that way if possible.
return a ** b._numerator return a ** b._numerator

View file

@ -237,9 +237,8 @@ class CAPITest(unittest.TestCase):
x = X() x = X()
self.assertEqual(power(4, x), (x, 4)) self.assertEqual(power(4, x), (x, 4))
self.assertEqual(inplacepower(4, x), (x, 4)) self.assertEqual(inplacepower(4, x), (x, 4))
# XXX: Three-arg power doesn't use __rpow__. self.assertEqual(power(4, x, 5), (x, 4, 5))
self.assertRaises(TypeError, power, 4, x, 5) self.assertEqual(inplacepower(4, x, 5), (x, 4, 5))
self.assertRaises(TypeError, inplacepower, 4, x, 5)
class X: class X:
def __ipow__(*args): def __ipow__(*args):

View file

@ -4493,12 +4493,10 @@ class Coverage:
self.assertIs(Decimal("NaN").fma(7, 1).is_nan(), True) self.assertIs(Decimal("NaN").fma(7, 1).is_nan(), True)
# three arg power # three arg power
self.assertEqual(pow(Decimal(10), 2, 7), 2) self.assertEqual(pow(Decimal(10), 2, 7), 2)
self.assertEqual(pow(10, Decimal(2), 7), 2)
if self.decimal == C: if self.decimal == C:
self.assertEqual(pow(10, Decimal(2), 7), 2)
self.assertEqual(pow(10, 2, Decimal(7)), 2) self.assertEqual(pow(10, 2, Decimal(7)), 2)
else: 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 # XXX: There is no special method to dispatch on the
# third arg of three-arg power. # third arg of three-arg power.
self.assertRaises(TypeError, pow, 10, 2, Decimal(7)) self.assertRaises(TypeError, pow, 10, 2, Decimal(7))

View file

@ -3515,6 +3515,10 @@ class ClassPropertiesAndMethods(unittest.TestCase):
self.assertEqual(repr(2 ** I(3)), "I(8)") self.assertEqual(repr(2 ** I(3)), "I(8)")
self.assertEqual(repr(I(2) ** 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), 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): class S(str):
def __eq__(self, other): def __eq__(self, other):
return self.lower() == other.lower() return self.lower() == other.lower()

View file

@ -1707,6 +1707,12 @@ class FractionTest(unittest.TestCase):
self.assertRaisesMessage(TypeError, self.assertRaisesMessage(TypeError,
message % ("Fraction", "int", "int"), message % ("Fraction", "int", "int"),
pow, F(3), 4, 5) 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__': if __name__ == '__main__':

View file

@ -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.

View file

@ -9992,13 +9992,46 @@ slot_nb_power(PyObject *self, PyObject *other, PyObject *modulus)
{ {
if (modulus == Py_None) if (modulus == Py_None)
return slot_nb_power_binary(self, other); 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 /* The following code is a copy of SLOT1BINFULL, but for three arguments. */
slot_nb_power, so check before calling self.__pow__. */ 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 && if (Py_TYPE(self)->tp_as_number != NULL &&
Py_TYPE(self)->tp_as_number->nb_power == slot_nb_power) { Py_TYPE(self)->tp_as_number->nb_power == slot_nb_power) {
PyObject* stack[3] = {self, other, modulus}; PyObject *r;
return vectorcall_method(&_Py_ID(__pow__), stack, 3); 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; Py_RETURN_NOTIMPLEMENTED;
} }