Skip to content

Should __rpow__() take modulo argument? #122193

Closed as not planned
Closed as not planned

Description

@Cartroo

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

Activity

  1. skirpichev commented on Jul 24, 2024

    @skirpichev
    Member

    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.

  2. added a commit that references this issue on Jul 25, 2024
  3. encukou commented on Jul 27, 2024

    @encukou
    Member

    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.

  4. skirpichev commented on Jul 27, 2024

    @skirpichev
    Member

    @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.

  5. skirpichev commented on Feb 14, 2025

    @skirpichev
    Member

    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?

  6. picnixz commented on Feb 14, 2025

    @picnixz
    Member

    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.

  7. skirpichev commented on Feb 15, 2025

    @skirpichev
    Member

    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.

  8. skirpichev commented on Feb 19, 2025

    @skirpichev
    Member

    closing in favor of #130104

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    docsDocumentation in the Doc dir

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions