-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
bpo-38302. __rpow__ now called when __ipow__ returns NotImplemented #16459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks good! Just noticed a couple of small things:
Objects/abstract.c
Outdated
@@ -1216,7 +1235,7 @@ PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z) | |||
{ | |||
if (v->ob_type->tp_as_number && | |||
v->ob_type->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), "**="); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PyNumber_InPlacePower
can now just mirror the other PyNumber_InPlace*
functions, right?
So this is the only line that's needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, thank you.
@@ -3900,6 +3900,18 @@ def __ipow__(self, other): | |||
a = C() | |||
a **= 2 | |||
|
|||
def test_ipow_returns_not_implemented(self): | |||
class A: | |||
def __ipow__(self, other): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we're also lacking a test for when A
doesn't define __ipow__
. Do you mind adding one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, added the test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a test for the two fallbacks and ensure the same class is returning NotImplemented
and implementing the regular power methods i.e something like:
class A:
def __ipow__(self, other):
return NotImplemented
class B(A):
def __pow__(self, other):
return 1
class C(A):
def __rpow__(self, other):
return 2
The linked bug is only exhibited when a class has an ipow slot but when called it returns NotImplemented
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understood correctly what you meant, but I updated the test to also test the __pow__
fallback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, what you have now looks good. What I meant was that to catch the original bug/properly be a regression test, the class has to look like:
class A:
def __ipow__(self, _):
return NotImplemented
def __pow__(self, other):
...
if the class looks like
class A:
def __pow__(self, other):
...
then the old logic returns a false here and correctly goes to the non-inplace version of power anyway:
Lines 1162 to 1163 in 8e19c8b
if (Py_TYPE(v)->tp_as_number && | |
Py_TYPE(v)->tp_as_number->nb_inplace_power != NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, you are right. Thanks!
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ashkop, one more small thing in the new test. Then it looks good!
Lib/test/test_descr.py
Outdated
def __rpow__(self, other): | ||
return 1 | ||
|
||
a = A() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can just be:
a = A() | |
a = object() |
Then you don't need the class definition above!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. 👍
@@ -3900,6 +3900,18 @@ def __ipow__(self, other): | |||
a = C() | |||
a **= 2 | |||
|
|||
def test_ipow_returns_not_implemented(self): | |||
class A: | |||
def __ipow__(self, other): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a test for the two fallbacks and ensure the same class is returning NotImplemented
and implementing the regular power methods i.e something like:
class A:
def __ipow__(self, other):
return NotImplemented
class B(A):
def __pow__(self, other):
return 1
class C(A):
def __rpow__(self, other):
return 2
The linked bug is only exhibited when a class has an ipow slot but when called it returns NotImplemented
.
@@ -0,0 +1 @@ | |||
If :func:`object.__ipow__` returns :const:`NotImplemented` :func:`object.__rpow__` will be called now as expected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just fyi, @brettcannon posted on the original bug pointing out that this would not call __pow__
either. I'd recommend changing this to
If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator will correctly
fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected.
It might also be worth mentioning this in whatsnew, even though it's obscure it is potentially a change in behavior that people should be informed about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the news entry, and also added a line to What's New
@@ -965,8 +965,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: " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this! Really weird that op_name
went unused so long. Would you mind adding a test for this like:
x = None
with self.assertRaises(TypeError) as cm:
x **= 2
self.assertIn('unsupported operand type(s) for **=', str(cm.exception))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, added a new test
a2476e7
to
25f95b5
Compare
@@ -3900,6 +3900,18 @@ def __ipow__(self, other): | |||
a = C() | |||
a **= 2 | |||
|
|||
def test_ipow_returns_not_implemented(self): | |||
class A: | |||
def __ipow__(self, other): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, what you have now looks good. What I meant was that to catch the original bug/properly be a regression test, the class has to look like:
class A:
def __ipow__(self, _):
return NotImplemented
def __pow__(self, other):
...
if the class looks like
class A:
def __pow__(self, other):
...
then the old logic returns a false here and correctly goes to the non-inplace version of power anyway:
Lines 1162 to 1163 in 8e19c8b
if (Py_TYPE(v)->tp_as_number && | |
Py_TYPE(v)->tp_as_number->nb_inplace_power != NULL) { |
Merged upstream, added |
I also tested this PR against my own test suite that uncovered this issue and it passed! |
@brettcannon: Please replace |
@ashkop thanks for the PR! |
Basically copied the behavior of
binary_iop
for the ternary operator. Also made sure thatop_name
is used in error message.https://bugs.python.org/issue38302