Skip to content

gh-130104: Call __rpow__ in ternary pow() if necessary - #130251

Merged
serhiy-storchaka merged 5 commits into
python:mainfrom
serhiy-storchaka:rpow
Apr 16, 2025
Merged

gh-130104: Call __rpow__ in ternary pow() if necessary#130251
serhiy-storchaka merged 5 commits into
python:mainfrom
serhiy-storchaka:rpow

Conversation

@serhiy-storchaka

@serhiy-storchaka serhiy-storchaka commented Feb 18, 2025

Copy link
Copy Markdown
Member

Previously it was only called in binary pow() and the binary power operator.


📚 Documentation preview 📚: https://cpython-previews--130251.org.readthedocs.build/

Previously it was only called in binary pow() and the binary
power operator.
Comment thread Objects/typeobject.c Outdated
@skirpichev
skirpichev self-requested a review February 19, 2025 00:16

@skirpichev skirpichev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM (few nitpicks)

Maybe it's possible to refactor code to avoid a copy of SLOT1BINFULL, but I doubt it worth efforts.

is a subclass of ``type(x)``. [#]_

.. index:: pair: built-in function; pow
Note that :meth:`__rpow__` should be defined to accept an optional third

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe you should use object.__rpow__(self, other, modulo=None) signature?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I do not understand you. Were and how can it be used?

This sentence is a copy of the corresponding sentence for __pow__.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry, I meant L3329-3342 above.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I see. There are other similar cases (for example __round__), so I will left this for other issue. Actually, __pow__() and __round__() are never called with None, so it is not necessary that they support None.

Comment thread Doc/reference/datamodel.rst Outdated
@skirpichev

Copy link
Copy Markdown
Member

CC @picnixz (this is an alternative to #122193)

@picnixz
picnixz self-requested a review March 2, 2025 13:14

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

First round of style review only. I'll look at the implementation and implications more in detail either later or tomorrow.

Comment thread Doc/whatsnew/3.14.rst
Comment on lines +335 to +339
* Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
Previously it was only called in two-argument :func:`!pow` and the binary
power operator.
(Contributed by Serhiy Storchaka in :gh:`130104`.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
Previously it was only called in two-argument :func:`!pow` and the binary
power operator.
(Contributed by Serhiy Storchaka in :gh:`130104`.)
* Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
Previously it was only called in two-argument :func:`!pow` and the binary
power operator.
(Contributed by Serhiy Storchaka in :gh:`130104`.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

What is the difference?

Comment thread Objects/typeobject.c
@@ -9813,13 +9813,46 @@ slot_nb_power(PyObject *self, PyObject *other, PyObject *modulus)
{
if (modulus == Py_None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's insert some PEP-7 here if (modulus == Py_None) { ...

Comment thread Objects/typeobject.c
Comment on lines +9820 to +9822
int do_other = !Py_IS_TYPE(self, Py_TYPE(other)) &&
Py_TYPE(other)->tp_as_number != NULL &&
Py_TYPE(other)->tp_as_number->nb_power == slot_nb_power;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We use a lot of Py_TYPE(other), so I think we can have a variable holding it just for readability.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This code is a copy of SLOT1BINFULL. I do not want to introduce more difference than necessary.

We can make SLOT1BINFULL supporting the third argument, but I am not sure that it is worth.

Comment thread Objects/typeobject.c
Comment on lines +9820 to +9822
int do_other = !Py_IS_TYPE(self, Py_TYPE(other)) &&
Py_TYPE(other)->tp_as_number != NULL &&
Py_TYPE(other)->tp_as_number->nb_power == slot_nb_power;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
int do_other = !Py_IS_TYPE(self, Py_TYPE(other)) &&
Py_TYPE(other)->tp_as_number != NULL &&
Py_TYPE(other)->tp_as_number->nb_power == slot_nb_power;
int do_other = !Py_IS_TYPE(self, Py_TYPE(other))
&& Py_TYPE(other)->tp_as_number != NULL
&& Py_TYPE(other)->tp_as_number->nb_power == slot_nb_power;

Comment thread Objects/typeobject.c
slot_nb_power, so check before calling self.__pow__. */

/* The following code is a copy of SLOT1BINFULL, but for three arguments. */
PyObject* stack[3];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
PyObject* stack[3];
PyObject *stack[3];

Comment thread Objects/typeobject.c
Comment on lines +9836 to +9837
if (r != Py_NotImplemented)
return r;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if (r != Py_NotImplemented)
return r;
if (r != Py_NotImplemented) {
return r;
}

Comment thread Objects/typeobject.c
Comment on lines +9846 to +9848
if (r != Py_NotImplemented ||
Py_IS_TYPE(other, Py_TYPE(self)))
return r;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if (r != Py_NotImplemented ||
Py_IS_TYPE(other, Py_TYPE(self)))
return r;
if (r != Py_NotImplemented || Py_IS_TYPE(other, Py_TYPE(self))) {
return r;
}

@picnixz

picnixz commented Mar 4, 2025

Copy link
Copy Markdown
Member

Sorry I didn't have time. I will leave for 10 days so I won't be able to review it. If you and Serhiy thinks it's good, then go ahead. If the PR is still not merged when I'm back then I'll try to review it.

As for whether I find it useful or not, my answer would be "yes".

@serhiy-storchaka
serhiy-storchaka merged commit 62ff86f into python:main Apr 16, 2025
@serhiy-storchaka
serhiy-storchaka deleted the rpow branch April 16, 2025 15:32
shaswat28 added a commit to shaswat28/mypy that referenced this pull request Aug 7, 2026
Checking a reverse operator method validates it against a two-positional-
argument callable, and it runs once per overload item. An overloaded
__rpow__ declaring both a two-argument and a three-argument variant was
therefore rejected, even though that is a correct signature: binary '**'
uses the two-argument variant, and since Python 3.14 ternary pow() may
call __rpow__ with a 'modulo' argument (python/cpython#130251).

Accept the three-argument form for __rpow__ when the definition is an
overload item. A lone __rpow__ that requires 'modulo' is still an error,
because binary '**' calls __rpow__ with a single argument and so can
never call it successfully.

Refs python#10786.
shaswat28 added a commit to shaswat28/mypy that referenced this pull request Aug 7, 2026
Checking a reverse operator method validates it against a two-positional-
argument callable, and it runs once per overload item. An overloaded
__rpow__ declaring both a two-argument and a three-argument variant was
therefore rejected, even though that is a correct signature: binary '**'
uses the two-argument variant, and since Python 3.14 ternary pow() may
call __rpow__ with a 'modulo' argument (python/cpython#130251).

Accept the three-argument form for __rpow__ when the definition is an
overload item. A lone __rpow__ that requires 'modulo' is still an error,
because binary '**' calls __rpow__ with a single argument and so can
never call it successfully.

Refs python#10786.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants