Skip to content

Commit 85192cb

Browse files
gh-130104: Call __rpow__ in ternary pow() if necessary
Previously it was only called in binary pow() and the binary power operator.
1 parent b93b7e5 commit 85192cb

File tree

10 files changed

+74
-18
lines changed

10 files changed

+74
-18
lines changed

Doc/reference/datamodel.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3356,10 +3356,15 @@ left undefined.
33563356
is called if ``type(x).__sub__(x, y)`` returns :data:`NotImplemented` or ``type(y)``
33573357
is a subclass of ``type(x)``. [#]_
33583358

3359-
.. index:: pair: built-in function; pow
3359+
Note that :meth:`__rpow__` should be defined to accept an optional third
3360+
argument if the ternary version of the built-in :func:`pow` function
3361+
is to be supported.
33603362

3361-
Note that ternary :func:`pow` will not try calling :meth:`__rpow__` (the
3362-
coercion rules would become too complicated).
3363+
.. versionchanged:: next
3364+
3365+
Ternary :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
3366+
Previously it was only called in binary :func:`!pow` and the binary
3367+
power operator.
33633368

33643369
.. note::
33653370

Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,11 @@ Other language changes
307307
The testbed can also be used to run the test suite of projects other than
308308
CPython itself. (Contributed by Russell Keith-Magee in :gh:`127592`.)
309309

310+
* Ternary :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
311+
Previously it was only called in binary :func:`!pow` and the binary
312+
power operator.
313+
(Contributed by Serhiy Storchaka in :gh:`130104`.)
314+
310315
New modules
311316
===========
312317

Lib/_pydecimal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,12 +2440,12 @@ def __pow__(self, other, modulo=None, context=None):
24402440

24412441
return ans
24422442

2443-
def __rpow__(self, other, context=None):
2443+
def __rpow__(self, other, modulo=None, context=None):
24442444
"""Swaps self/other and returns __pow__."""
24452445
other = _convert_other(other)
24462446
if other is NotImplemented:
24472447
return other
2448-
return other.__pow__(self, context=context)
2448+
return other.__pow__(self, modulo, context=context)
24492449

24502450
def normalize(self, context=None):
24512451
"""Normalize- strip trailing 0s, change anything equal to 0 to 0e0"""

Lib/fractions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,8 +905,10 @@ def __pow__(a, b, modulo=None):
905905
else:
906906
return NotImplemented
907907

908-
def __rpow__(b, a):
908+
def __rpow__(b, a, modulo=None):
909909
"""a ** b"""
910+
if modulo is not None:
911+
return NotImplemented
910912
if b._denominator == 1 and b._numerator >= 0:
911913
# If a is an int, keep it that way if possible.
912914
return a ** b._numerator

Lib/test/test_capi/test_number.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,8 @@ def __rpow__(*args):
238238
x = X()
239239
self.assertEqual(power(4, x), (x, 4))
240240
self.assertEqual(inplacepower(4, x), (x, 4))
241-
# XXX: Three-arg power doesn't use __rpow__.
242-
self.assertRaises(TypeError, power, 4, x, 5)
243-
self.assertRaises(TypeError, inplacepower, 4, x, 5)
241+
self.assertEqual(power(4, x, 5), (x, 4, 5))
242+
self.assertEqual(inplacepower(4, x, 5), (x, 4, 5))
244243

245244
class X:
246245
def __ipow__(*args):

Lib/test/test_decimal.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4481,12 +4481,10 @@ def test_implicit_context(self):
44814481
self.assertIs(Decimal("NaN").fma(7, 1).is_nan(), True)
44824482
# three arg power
44834483
self.assertEqual(pow(Decimal(10), 2, 7), 2)
4484+
self.assertEqual(pow(10, Decimal(2), 7), 2)
44844485
if self.decimal == C:
4485-
self.assertEqual(pow(10, Decimal(2), 7), 2)
44864486
self.assertEqual(pow(10, 2, Decimal(7)), 2)
44874487
else:
4488-
# XXX: Three-arg power doesn't use __rpow__.
4489-
self.assertRaises(TypeError, pow, 10, Decimal(2), 7)
44904488
# XXX: There is no special method to dispatch on the
44914489
# third arg of three-arg power.
44924490
self.assertRaises(TypeError, pow, 10, 2, Decimal(7))

Lib/test/test_descr.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3515,6 +3515,10 @@ def __rpow__(self, other, mod=None):
35153515
self.assertEqual(repr(2 ** I(3)), "I(8)")
35163516
self.assertEqual(repr(I(2) ** 3), "I(8)")
35173517
self.assertEqual(repr(pow(I(2), I(3), I(5))), "I(3)")
3518+
self.assertEqual(repr(pow(I(2), I(3), 5)), "I(3)")
3519+
self.assertEqual(repr(pow(I(2), 3, 5)), "I(3)")
3520+
self.assertEqual(repr(pow(2, I(3), 5)), "I(3)")
3521+
self.assertEqual(repr(pow(2, 3, I(5))), "3")
35183522
class S(str):
35193523
def __eq__(self, other):
35203524
return self.lower() == other.lower()

Lib/test/test_fractions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,6 +1707,12 @@ def test_three_argument_pow(self):
17071707
self.assertRaisesMessage(TypeError,
17081708
message % ("Fraction", "int", "int"),
17091709
pow, F(3), 4, 5)
1710+
self.assertRaisesMessage(TypeError,
1711+
message % ("int", "Fraction", "int"),
1712+
pow, 3, F(4), 5)
1713+
self.assertRaisesMessage(TypeError,
1714+
message % ("int", "int", "Fraction"),
1715+
pow, 3, 4, F(5))
17101716

17111717

17121718
if __name__ == '__main__':
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Ternary :func:`pow` now try calling :meth:`~object.__rpow__` if
2+
necessary.
3+
Previously it was only called in binary :func:`!pow` and the binary
4+
power operator.

Objects/typeobject.c

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9628,7 +9628,7 @@ FUNCNAME(PyObject *self, PyObject *other) \
96289628
{ \
96299629
PyObject* stack[2]; \
96309630
PyThreadState *tstate = _PyThreadState_GET(); \
9631-
int do_other = !Py_IS_TYPE(self, Py_TYPE(other)) && \
9631+
int do_other = /*(void*)TESTFUNC != (void*)slot_nb_power &&*/ !Py_IS_TYPE(self, Py_TYPE(other)) && \
96329632
Py_TYPE(other)->tp_as_number != NULL && \
96339633
Py_TYPE(other)->tp_as_number->SLOTNAME == TESTFUNC; \
96349634
if (Py_TYPE(self)->tp_as_number != NULL && \
@@ -9813,13 +9813,46 @@ slot_nb_power(PyObject *self, PyObject *other, PyObject *modulus)
98139813
{
98149814
if (modulus == Py_None)
98159815
return slot_nb_power_binary(self, other);
9816-
/* Three-arg power doesn't use __rpow__. But ternary_op
9817-
can call this when the second argument's type uses
9818-
slot_nb_power, so check before calling self.__pow__. */
9816+
9817+
/* The following code is a copy of SLOT1BINFULL, but for three arguments. */
9818+
PyObject* stack[3];
9819+
PyThreadState *tstate = _PyThreadState_GET();
9820+
int do_other = !Py_IS_TYPE(self, Py_TYPE(other)) &&
9821+
Py_TYPE(other)->tp_as_number != NULL &&
9822+
Py_TYPE(other)->tp_as_number->nb_power == slot_nb_power;
98199823
if (Py_TYPE(self)->tp_as_number != NULL &&
98209824
Py_TYPE(self)->tp_as_number->nb_power == slot_nb_power) {
9821-
PyObject* stack[3] = {self, other, modulus};
9822-
return vectorcall_method(&_Py_ID(__pow__), stack, 3);
9825+
PyObject *r;
9826+
if (do_other && PyType_IsSubtype(Py_TYPE(other), Py_TYPE(self))) {
9827+
int ok = method_is_overloaded(self, other, &_Py_ID(__rpow__));
9828+
if (ok < 0) {
9829+
return NULL;
9830+
}
9831+
if (ok) {
9832+
stack[0] = other;
9833+
stack[1] = self;
9834+
stack[2] = modulus;
9835+
r = vectorcall_maybe(tstate, &_Py_ID(__rpow__), stack, 3);
9836+
if (r != Py_NotImplemented)
9837+
return r;
9838+
Py_DECREF(r);
9839+
do_other = 0;
9840+
}
9841+
}
9842+
stack[0] = self;
9843+
stack[1] = other;
9844+
stack[2] = modulus;
9845+
r = vectorcall_maybe(tstate, &_Py_ID(__pow__), stack, 3);
9846+
if (r != Py_NotImplemented ||
9847+
Py_IS_TYPE(other, Py_TYPE(self)))
9848+
return r;
9849+
Py_DECREF(r);
9850+
}
9851+
if (do_other) {
9852+
stack[0] = other;
9853+
stack[1] = self;
9854+
stack[2] = modulus;
9855+
return vectorcall_maybe(tstate, &_Py_ID(__rpow__), stack, 3);
98239856
}
98249857
Py_RETURN_NOTIMPLEMENTED;
98259858
}

0 commit comments

Comments
 (0)