Skip to content

Conversation

@ashkop
Copy link
Contributor

@ashkop ashkop commented Sep 28, 2019

Basically copied the behavior of binary_iop for the ternary operator. Also made sure that op_name is used in error message.

https://bugs.python.org/issue38302

Copy link
Member

@brandtbucher brandtbucher left a 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:

Copy link
Member

@brandtbucher brandtbucher Sep 28, 2019

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, thank you.

Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, added the test

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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:

cpython/Objects/abstract.c

Lines 1162 to 1163 in 8e19c8b

if (Py_TYPE(v)->tp_as_number &&
Py_TYPE(v)->tp_as_number->nb_inplace_power != NULL) {

Copy link
Contributor Author

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!

@brandtbucher brandtbucher added the type-bug An unexpected behavior, bug, or error label Sep 28, 2019
@hongweipeng

This comment has been minimized.

Copy link
Member

@brandtbucher brandtbucher left a 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!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be:

Suggested change
a = A()
a = object()

Then you don't need the class definition above!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. 👍

Copy link
Member

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.

Copy link
Member

@ammaraskar ammaraskar Aug 24, 2020

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.

Copy link
Contributor Author

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

Copy link
Member

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))

Copy link
Contributor Author

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

@ashkop ashkop force-pushed the bpo-38302-ipow-fix branch from a2476e7 to 25f95b5 Compare August 25, 2020 15:39
Copy link
Member

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:

cpython/Objects/abstract.c

Lines 1162 to 1163 in 8e19c8b

if (Py_TYPE(v)->tp_as_number &&
Py_TYPE(v)->tp_as_number->nb_inplace_power != NULL) {

@ashkop
Copy link
Contributor Author

ashkop commented Feb 22, 2021

Merged upstream, added Py_TEST usage, removed TERNARY_OP definition since op_name is now used in the error message.

@brettcannon brettcannon self-requested a review February 23, 2021 01:15
@brettcannon
Copy link
Member

I also tested this PR against my own test suite that uncovered this issue and it passed!

@brettcannon brettcannon merged commit cc02b4f into python:master Feb 26, 2021
@bedevere-bot
Copy link

@brettcannon: Please replace # with GH- in the commit message next time. Thanks!

@brettcannon
Copy link
Member

@ashkop thanks for the PR!

adorilson pushed a commit to adorilson/cpython that referenced this pull request Mar 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-bug An unexpected behavior, bug, or error

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants