From 75aba4a6fe7c96c64eb1335f453bb138a390d3f5 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Sep 2019 14:51:27 +0300 Subject: [PATCH 1/8] bpo-38302. __rpow__ now called when __ipow__ returns NotImplemented --- Lib/test/test_descr.py | 12 ++++++++++++ Objects/abstract.c | 23 +++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 307416c3300ae3..597f0099d7c704 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3893,6 +3893,18 @@ def __ipow__(self, other): a = C() a **= 2 + def test_ipow_returns_not_implemented(self): + class A: + def __ipow__(self, other): + return NotImplemented + class B: + def __rpow__(self, other): + return 1 + a = A() + b = B() + a **= b + self.assertEqual(a, 1) + def test_mutable_bases(self): # Testing mutable bases... diff --git a/Objects/abstract.c b/Objects/abstract.c index 7bd72c9b5dcc26..3a3416770f4f9b 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -910,8 +910,9 @@ ternary_op(PyObject *v, if (z == Py_None) PyErr_Format( PyExc_TypeError, - "unsupported operand type(s) for ** or pow(): " + "unsupported operand type(s) for %.100s: " "'%.100s' and '%.100s'", + op_name, Py_TYPE(v)->tp_name, Py_TYPE(w)->tp_name); else @@ -1064,6 +1065,24 @@ binary_iop(PyObject *v, PyObject *w, const int iop_slot, const int op_slot, return result; } +static PyObject * +ternary_iop(PyObject *v, PyObject *w, PyObject *z, const int iop_slot, const int op_slot, + const char *op_name) +{ + PyNumberMethods *mv = v->ob_type->tp_as_number; + if (mv != NULL) { + ternaryfunc slot = NB_TERNOP(mv, iop_slot); + if (slot) { + PyObject *x = (slot)(v, w, z); + if (x != Py_NotImplemented) { + return x; + } + Py_DECREF(x); + } + } + return ternary_op(v, w, z, op_slot, op_name); +} + #define INPLACE_BINOP(func, iop, op, op_name) \ PyObject * \ func(PyObject *v, PyObject *w) { \ @@ -1161,7 +1180,7 @@ PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z) { if (Py_TYPE(v)->tp_as_number && Py_TYPE(v)->tp_as_number->nb_inplace_power != NULL) { - return ternary_op(v, w, z, NB_SLOT(nb_inplace_power), "**="); + return ternary_iop(v, w, z, NB_SLOT(nb_inplace_power), NB_SLOT(nb_power), "**="); } else { return ternary_op(v, w, z, NB_SLOT(nb_power), "**="); From c981f0da3aa5a047b9b9037ef0859c1b33769b7f Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2019 12:23:24 +0000 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst b/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst new file mode 100644 index 00000000000000..b24e04b0d0aaad --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst @@ -0,0 +1 @@ +If :func:`operator.__ipow__` returns :const:`NotImplemented` :func:`operator.__rpow__` will be called now as expected. \ No newline at end of file From 9005ccea217ef18a9bdc37377a78fbb2a708049e Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 29 Sep 2019 10:35:28 +0300 Subject: [PATCH 3/8] Fix review comments --- Lib/test/test_descr.py | 13 +++++++++++++ Objects/abstract.c | 9 ++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 597f0099d7c704..767c2fefaf56f4 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3905,6 +3905,19 @@ def __rpow__(self, other): a **= b self.assertEqual(a, 1) + def test_no_ipow(self): + class A: + pass + + class B: + def __rpow__(self, other): + return 1 + + a = A() + b = B() + a **= b + self.assertEqual(a, 1) + def test_mutable_bases(self): # Testing mutable bases... diff --git a/Objects/abstract.c b/Objects/abstract.c index 3a3416770f4f9b..e536bab034e42d 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1178,13 +1178,8 @@ PyNumber_InPlaceRemainder(PyObject *v, PyObject *w) PyObject * PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z) { - if (Py_TYPE(v)->tp_as_number && - Py_TYPE(v)->tp_as_number->nb_inplace_power != NULL) { - return ternary_iop(v, w, z, NB_SLOT(nb_inplace_power), NB_SLOT(nb_power), "**="); - } - else { - return ternary_op(v, w, z, NB_SLOT(nb_power), "**="); - } + return ternary_iop(v, w, z, NB_SLOT(nb_inplace_power), + NB_SLOT(nb_power), "**="); } From 8a357bb814d5daa64355d07dbe937134ec01ec44 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 29 Sep 2019 21:59:52 +0300 Subject: [PATCH 4/8] Use `object()` in test --- Lib/test/test_descr.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 767c2fefaf56f4..cca35abb97e2fd 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3906,14 +3906,11 @@ def __rpow__(self, other): self.assertEqual(a, 1) def test_no_ipow(self): - class A: - pass - class B: def __rpow__(self, other): return 1 - a = A() + a = object() b = B() a **= b self.assertEqual(a, 1) From 6426025d8781972c199cfe4b5ae2ba0ad809cd13 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 25 Aug 2020 17:05:08 +0300 Subject: [PATCH 5/8] Add tests, update news entry --- Lib/test/test_descr.py | 22 ++++++++++++++++++- .../2019-09-28-12-23-23.bpo-38302.hsCNgX.rst | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index cca35abb97e2fd..e73ba5d13ae493 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3897,14 +3897,24 @@ def test_ipow_returns_not_implemented(self): class A: def __ipow__(self, other): return NotImplemented - class B: + + class B(A): def __rpow__(self, other): return 1 + + class C(A): + def __pow__(self, other): + return 2 a = A() b = B() + c = C() + a **= b self.assertEqual(a, 1) + c **= b + self.assertEqual(c, 2) + def test_no_ipow(self): class B: def __rpow__(self, other): @@ -3915,6 +3925,16 @@ def __rpow__(self, other): a **= b self.assertEqual(a, 1) + def test_ipow_exception_text(self): + x = None + with self.assertRaises(TypeError) as cm: + x **= 2 + self.assertIn('unsupported operand type(s) for **=', str(cm.exception)) + + with self.assertRaises(TypeError) as cm: + y = x ** 2 + self.assertIn('unsupported operand type(s) for **', str(cm.exception)) + def test_mutable_bases(self): # Testing mutable bases... diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst b/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst index b24e04b0d0aaad..e9462f1facd8fc 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2019-09-28-12-23-23.bpo-38302.hsCNgX.rst @@ -1 +1 @@ -If :func:`operator.__ipow__` returns :const:`NotImplemented` :func:`operator.__rpow__` will be called now as expected. \ No newline at end of file +If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator will correctly fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected. \ No newline at end of file From 84241a21d47985598afb85a9f5525bd63f64ca77 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 25 Aug 2020 17:32:00 +0300 Subject: [PATCH 6/8] Add whatsnew entry --- Doc/whatsnew/3.10.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index eb5ae01a7c04d4..2acf29600f8400 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -92,6 +92,9 @@ Other Language Changes the :meth:`~object.__int__` method but do not have the :meth:`~object.__index__` method). (Contributed by Serhiy Storchaka in :issue:`37999`.) +* If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator will + correctly fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected. + (Contributed by Alex Shkop in :issue:`38302`.) New Modules From 25f95b5982b46d6972e7dc5a9c81e9d4f07cfd05 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 25 Aug 2020 17:33:41 +0300 Subject: [PATCH 7/8] Fix whitespace --- Lib/test/test_descr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index e73ba5d13ae493..7ffceab183c279 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3897,7 +3897,7 @@ def test_ipow_returns_not_implemented(self): class A: def __ipow__(self, other): return NotImplemented - + class B(A): def __rpow__(self, other): return 1 From 80cad6c1793f29782bd68083f3712fe0ad6f8552 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 22 Feb 2021 11:06:03 +0200 Subject: [PATCH 8/8] Remove TERNARY_OP since op_name is now used in error message --- Objects/abstract.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index eecf2d4949a78d..4cd59100ddc567 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -882,10 +882,8 @@ static PyObject * ternary_op(PyObject *v, PyObject *w, PyObject *z, - const int op_slot -#ifndef NDEBUG - , const char *op_name -#endif + const int op_slot, + const char *op_name ) { PyNumberMethods *mv = Py_TYPE(v)->tp_as_number; @@ -964,8 +962,9 @@ ternary_op(PyObject *v, else { PyErr_Format( PyExc_TypeError, - "unsupported operand type(s) for pow(): " + "unsupported operand type(s) for %.100s: " "'%.100s', '%.100s', '%.100s'", + op_name, Py_TYPE(v)->tp_name, Py_TYPE(w)->tp_name, Py_TYPE(z)->tp_name); @@ -973,13 +972,6 @@ ternary_op(PyObject *v, return NULL; } -#ifdef NDEBUG -# define TERNARY_OP(v, w, z, op_slot, op_name) ternary_op(v, w, z, op_slot) -#else -# define TERNARY_OP(v, w, z, op_slot, op_name) ternary_op(v, w, z, op_slot, op_name) -#endif - - #define BINARY_FUNC(func, op, op_name) \ PyObject * \ func(PyObject *v, PyObject *w) { \ @@ -1078,7 +1070,7 @@ PyNumber_Remainder(PyObject *v, PyObject *w) PyObject * PyNumber_Power(PyObject *v, PyObject *w, PyObject *z) { - return TERNARY_OP(v, w, z, NB_SLOT(nb_power), "** or pow()"); + return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()"); } /* Binary in-place operators */