Skip to content

Commit cc02b4f

Browse files
authored
bpo-38302: __pow__/__rpow__ now called when __ipow__ returns NotImplemented (#16459)
1 parent 5e48e83 commit cc02b4f

File tree

4 files changed

+73
-21
lines changed

4 files changed

+73
-21
lines changed

Doc/whatsnew/3.10.rst

+3
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ Other Language Changes
276276
the :meth:`~object.__int__` method but do not have the
277277
:meth:`~object.__index__` method).
278278
(Contributed by Serhiy Storchaka in :issue:`37999`.)
279+
* If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator will
280+
correctly fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected.
281+
(Contributed by Alex Shkop in :issue:`38302`.)
279282
280283
* Assignment expressions can now be used unparenthesized within set literals
281284
and set comprehensions, as well as in sequence indexes (but not slices).

Lib/test/test_descr.py

+42
Original file line numberDiff line numberDiff line change
@@ -3903,6 +3903,48 @@ def __ipow__(self, other):
39033903
a = C()
39043904
a **= 2
39053905

3906+
def test_ipow_returns_not_implemented(self):
3907+
class A:
3908+
def __ipow__(self, other):
3909+
return NotImplemented
3910+
3911+
class B(A):
3912+
def __rpow__(self, other):
3913+
return 1
3914+
3915+
class C(A):
3916+
def __pow__(self, other):
3917+
return 2
3918+
a = A()
3919+
b = B()
3920+
c = C()
3921+
3922+
a **= b
3923+
self.assertEqual(a, 1)
3924+
3925+
c **= b
3926+
self.assertEqual(c, 2)
3927+
3928+
def test_no_ipow(self):
3929+
class B:
3930+
def __rpow__(self, other):
3931+
return 1
3932+
3933+
a = object()
3934+
b = B()
3935+
a **= b
3936+
self.assertEqual(a, 1)
3937+
3938+
def test_ipow_exception_text(self):
3939+
x = None
3940+
with self.assertRaises(TypeError) as cm:
3941+
x **= 2
3942+
self.assertIn('unsupported operand type(s) for **=', str(cm.exception))
3943+
3944+
with self.assertRaises(TypeError) as cm:
3945+
y = x ** 2
3946+
self.assertIn('unsupported operand type(s) for **', str(cm.exception))
3947+
39063948
def test_mutable_bases(self):
39073949
# Testing mutable bases...
39083950

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator will correctly fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected.

Objects/abstract.c

+27-21
Original file line numberDiff line numberDiff line change
@@ -882,10 +882,8 @@ static PyObject *
882882
ternary_op(PyObject *v,
883883
PyObject *w,
884884
PyObject *z,
885-
const int op_slot
886-
#ifndef NDEBUG
887-
, const char *op_name
888-
#endif
885+
const int op_slot,
886+
const char *op_name
889887
)
890888
{
891889
PyNumberMethods *mv = Py_TYPE(v)->tp_as_number;
@@ -955,30 +953,25 @@ ternary_op(PyObject *v,
955953
if (z == Py_None) {
956954
PyErr_Format(
957955
PyExc_TypeError,
958-
"unsupported operand type(s) for ** or pow(): "
956+
"unsupported operand type(s) for %.100s: "
959957
"'%.100s' and '%.100s'",
958+
op_name,
960959
Py_TYPE(v)->tp_name,
961960
Py_TYPE(w)->tp_name);
962961
}
963962
else {
964963
PyErr_Format(
965964
PyExc_TypeError,
966-
"unsupported operand type(s) for pow(): "
965+
"unsupported operand type(s) for %.100s: "
967966
"'%.100s', '%.100s', '%.100s'",
967+
op_name,
968968
Py_TYPE(v)->tp_name,
969969
Py_TYPE(w)->tp_name,
970970
Py_TYPE(z)->tp_name);
971971
}
972972
return NULL;
973973
}
974974

975-
#ifdef NDEBUG
976-
# define TERNARY_OP(v, w, z, op_slot, op_name) ternary_op(v, w, z, op_slot)
977-
#else
978-
# define TERNARY_OP(v, w, z, op_slot, op_name) ternary_op(v, w, z, op_slot, op_name)
979-
#endif
980-
981-
982975
#define BINARY_FUNC(func, op, op_name) \
983976
PyObject * \
984977
func(PyObject *v, PyObject *w) { \
@@ -1077,7 +1070,7 @@ PyNumber_Remainder(PyObject *v, PyObject *w)
10771070
PyObject *
10781071
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
10791072
{
1080-
return TERNARY_OP(v, w, z, NB_SLOT(nb_power), "** or pow()");
1073+
return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()");
10811074
}
10821075

10831076
/* Binary in-place operators */
@@ -1140,6 +1133,24 @@ binary_iop(PyObject *v, PyObject *w, const int iop_slot, const int op_slot,
11401133
return result;
11411134
}
11421135

1136+
static PyObject *
1137+
ternary_iop(PyObject *v, PyObject *w, PyObject *z, const int iop_slot, const int op_slot,
1138+
const char *op_name)
1139+
{
1140+
PyNumberMethods *mv = Py_TYPE(v)->tp_as_number;
1141+
if (mv != NULL) {
1142+
ternaryfunc slot = NB_TERNOP(mv, iop_slot);
1143+
if (slot) {
1144+
PyObject *x = (slot)(v, w, z);
1145+
if (x != Py_NotImplemented) {
1146+
return x;
1147+
}
1148+
Py_DECREF(x);
1149+
}
1150+
}
1151+
return ternary_op(v, w, z, op_slot, op_name);
1152+
}
1153+
11431154
#define INPLACE_BINOP(func, iop, op, op_name) \
11441155
PyObject * \
11451156
func(PyObject *v, PyObject *w) { \
@@ -1237,13 +1248,8 @@ PyNumber_InPlaceRemainder(PyObject *v, PyObject *w)
12371248
PyObject *
12381249
PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z)
12391250
{
1240-
if (Py_TYPE(v)->tp_as_number &&
1241-
Py_TYPE(v)->tp_as_number->nb_inplace_power != NULL) {
1242-
return TERNARY_OP(v, w, z, NB_SLOT(nb_inplace_power), "**=");
1243-
}
1244-
else {
1245-
return TERNARY_OP(v, w, z, NB_SLOT(nb_power), "**=");
1246-
}
1251+
return ternary_iop(v, w, z, NB_SLOT(nb_inplace_power),
1252+
NB_SLOT(nb_power), "**=");
12471253
}
12481254

12491255

0 commit comments

Comments
 (0)