Skip to content

Should __rpow__() take modulo argument? #122193

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

Closed
Cartroo opened this issue Jul 23, 2024 · 7 comments
Closed

Should __rpow__() take modulo argument? #122193

Cartroo opened this issue Jul 23, 2024 · 7 comments
Labels
docs Documentation in the Doc dir

Comments

@Cartroo
Copy link

Cartroo commented Jul 23, 2024

Documentation

The documentation for __rpow__() clearly states:

Note that ternary pow() will not try calling rpow() (the coercion rules would become too complicated).

As far as I'm aware (I may be wrong!) ternary pow() is the only way that the modulo parameter can be passed to __pow__(). Since the documentation explicitly states that ternary pow() will not fall back on __rpow__(), it seems misleading that the summary for __rpow__() above contains the same optional modulo parameter as for __pow__().

Should the modulo parameter for __rpow__() be removed? Or are there some means other than ternary pow() by which this parameter can end up being passed to __rpow__()?

Linked PRs

@Cartroo Cartroo added the docs Documentation in the Doc dir label Jul 23, 2024
@skirpichev
Copy link
Contributor

Or are there some means other than ternary pow() by which this parameter can end up being passed to rpow()?

I don't think so.

skirpichev added a commit to skirpichev/cpython that referenced this issue Jul 25, 2024
Docs says: "Note that ternary pow() will not try calling __rpow__() (the
coercion rules would become too complicated)."

Probably, this is not something, that might be changed (see recent
discussion in https://discuss.python.org/t/35185).  Lets simplify
signature of this dunder method.
@encukou
Copy link
Member

encukou commented Jul 27, 2024

Third-party arithmetic libraries are free to implement the complicated coercion rules (or simplify them for their use case), and call __rpow__ directly.

And anyone who implements __rpow__ is free to raise an error if the third argument is unimplemented or doesn't make sense.

@skirpichev
Copy link
Contributor

skirpichev commented Jul 27, 2024

@encukou, not sure I get your point.

Third-party arithmetic libraries are free to implement the complicated coercion rules (or simplify them for their use case), and call __rpow__ directly.

Of course, they are free to do such (strange) things too. But that means they can't use python's arithmetic operators and builtin functions, e.g. ** or pow().

And anyone who implements __rpow__

Using Python dunder methods in such a library will be only a source of confusion. I would count such cases as library bugs.

On another hand, coercion rules for ternary ops in the CPython could be treated as an implementation details (and that should be clearly stated in docs). Different implementations may take other decisions here. Sorry, but I doubt that this case does make sense.

@skirpichev
Copy link
Contributor

CC @picnixz

It seems, three-arg form __rpow__() does make sense for you, as you like Petr's comment. Could you elaborate this? There is no way to call this form indirectly (i.e. by pow()), only with an explicit call to this dunder. In that case I expect that people will use a dedicated public method instead. Or not?

@picnixz
Copy link
Member

picnixz commented Feb 14, 2025

Could you elaborate this? There is no way to call this form indirectly (i.e. by pow()), only with an explicit call to this dunder

At the time when I reacted with a 👍, I think this is what I had in mind.

In that case I expect that people will use a dedicated public method instead. Or not?

Yes and no I think. My reasoning was sa follows: it's possible to have some dispatcher mechanism, say

class Interface:
	def rpow(self, x, mod=None):
		# pre-validation of rpow()
		# then call the "fast" implementation directly.
		return self.__rpow__(x, mod)

	def __rpow__(self, x, mod=None):
		# actual implementation of rpow()
		...

From a design PoV, this could make sense to some users, where the dunder is the "fast & protected implementation, no redundant checks" while "rpow()" is the public method. So, even it our built-ins would never call __rpow__, someone might create an extended pow() function which does the job (and they would handle the coercion rules themselves, that's up to them). So, it's still make sense to allow the third argument, even if CPython itself doesn't use it.

I also think it's fine to keep identical input arguments for __pow__ and __rpow__. It's easier to remember one name and one list of parameters rather than "__pow__ has 3 arguments while __rpow__ has 2"

I think I mainly reacted 👍 to indicate that it's fine to keep a general form even though we don't use it ourselves.

@skirpichev
Copy link
Contributor

skirpichev commented Feb 15, 2025

even it our built-ins would never call __rpow__, someone might create an extended pow() function which does the job (and they would handle the coercion rules themselves, that's up to them).

This doesn't explain why someone will use poor dispatching mechanism, coming from Python, instead of proper multiple dispatch. And I guess nobody know real-world examples of this.

It's easier to remember one name and one list of parameters rather than "__pow__ has 3 arguments while __rpow__ has 2"

On another hand it's more difficult to remember semantics of __rpow__. If it has third argument, people will expect it to be passed in some scenario, which is not true.

@skirpichev
Copy link
Contributor

closing in favor of #130104

@skirpichev skirpichev closed this as not planned Won't fix, can't repro, duplicate, stale Feb 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir
Projects
None yet
Development

No branches or pull requests

4 participants