gh-130104: Call __rpow__ in ternary pow() if necessary - #130251
Conversation
Previously it was only called in binary pow() and the binary power operator.
skirpichev
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Maybe you should use object.__rpow__(self, other, modulo=None) signature?
There was a problem hiding this comment.
I do not understand you. Were and how can it be used?
This sentence is a copy of the corresponding sentence for __pow__.
There was a problem hiding this comment.
Sorry, I meant L3329-3342 above.
There was a problem hiding this comment.
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.
picnixz
left a comment
There was a problem hiding this comment.
First round of style review only. I'll look at the implementation and implications more in detail either later or tomorrow.
| * 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`.) | ||
|
|
There was a problem hiding this comment.
| * 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`.) | |
There was a problem hiding this comment.
What is the difference?
| @@ -9813,13 +9813,46 @@ slot_nb_power(PyObject *self, PyObject *other, PyObject *modulus) | |||
| { | |||
| if (modulus == Py_None) | |||
There was a problem hiding this comment.
Let's insert some PEP-7 here if (modulus == Py_None) { ...
| 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; |
There was a problem hiding this comment.
We use a lot of Py_TYPE(other), so I think we can have a variable holding it just for readability.
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
| 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; |
| slot_nb_power, so check before calling self.__pow__. */ | ||
|
|
||
| /* The following code is a copy of SLOT1BINFULL, but for three arguments. */ | ||
| PyObject* stack[3]; |
There was a problem hiding this comment.
| PyObject* stack[3]; | |
| PyObject *stack[3]; |
| if (r != Py_NotImplemented) | ||
| return r; |
There was a problem hiding this comment.
| if (r != Py_NotImplemented) | |
| return r; | |
| if (r != Py_NotImplemented) { | |
| return r; | |
| } |
| if (r != Py_NotImplemented || | ||
| Py_IS_TYPE(other, Py_TYPE(self))) | ||
| return r; |
There was a problem hiding this comment.
| 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; | |
| } |
|
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". |
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.
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.
Previously it was only called in binary pow() and the binary power operator.
📚 Documentation preview 📚: https://cpython-previews--130251.org.readthedocs.build/