[Note: I don't think this is a high priority issue, as it only relates
to doing mixed-type modular exponentiation, and that seems like an
inherently bad idea anyway. But I'm also curious if anyone is able to
explain the current behaviour, as I haven't come up with a better
explanation than "It doesn't actually work, but nobody needs it enough
to complain about it not working"]

While writing up bpo-39302 [1], I tried experimenting at the REPL to
see if I could get __pow__ to ever exercise the code paths where
3-argument pow() is handled by the exponent or the modulus (as
described in the source code and PEP 208), rather than the usual case
where it is handled by the base.

I failed - the only time I could get my custom __pow__ implementation
to run was when it was the base, never when it was the modulus or
exponent. This means that, as far as I'm able to tell, ternary_op in
abstract.c isn't actually working as intended, or at least, it doesn't
work in combination with the way the `nb_power` slot wrapper is
implemented in typeobject.c. That slot wrapper only ever calls the
Python level function provided by the first operand, even though
ternary_op calls 3 different slot implementations, they all ultimately
get resolved as attempts to call the same python level function (the
`__pow__` implementation on the base).

I couldn't find any test cases that exercise this functionality, so
even though PEP 208 describes the expected semantics, it isn't clear
whether or not the implementation has ever achieved them.

This is the session where I tried to see if I could get it to work the
way I expected it to work based on the comments in ternary_op:

[py3.7]> class _PowArgsMixin:
...     def __pow__(*args):
...         return tuple(map(type, args))
...
[py3.7]> class NoPow:
...     pass
...
[py3.7]> class NotImplementedPow:
...     def __pow__(*args):
...         return NotImplemented
...
[py3.7]> class UnrelatedPow(_PowArgsMixin):
...     pass
...
[py3.7]> class ChildOfNoPow(_PowArgsMixin, NoPow):
...     pass
...
[py3.7]> class ChildOfNotImplementedPow(_PowArgsMixin, NotImplementedPow):
...     pass
...
[py3.7]> no_pow = NoPow()
[py3.7]> ni_pow = NotImplementedPow()
[py3.7]> has_pow = UnrelatedPow()
[py3.7]> child_no = ChildOfNoPow()
[py3.7]> child_ni = ChildOfNotImplementedPow()

[py3.7]> # They all give the expected result as the base
... pow(has_pow, no_pow, no_pow)
(<class '__main__.UnrelatedPow'>, <class '__main__.NoPow'>, <class
'__main__.NoPow'>)
[py3.7]> pow(has_pow, ni_pow, ni_pow)
(<class '__main__.UnrelatedPow'>, <class
'__main__.NotImplementedPow'>, <class '__main__.NotImplementedPow'>)
[py3.7]> pow(child_no, no_pow, no_pow)
(<class '__main__.ChildOfNoPow'>, <class '__main__.NoPow'>, <class
'__main__.NoPow'>)
[py3.7]> pow(child_ni, ni_pow, ni_pow)
(<class '__main__.ChildOfNotImplementedPow'>, <class
'__main__.NotImplementedPow'>, <class '__main__.NotImplementedPow'>)

[py3.7]> # But none of them work when given as the exponent
... pow(no_pow, has_pow, no_pow)
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: unsupported operand type(s) for pow(): 'NoPow',
'UnrelatedPow', 'NoPow'
[py3.7]> pow(ni_pow, has_pow, ni_pow)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for pow(): 'NotImplementedPow',
'UnrelatedPow', 'NotImplementedPow'
[py3.7]> pow(no_pow, child_no, no_pow)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for pow(): 'NoPow',
'ChildOfNoPow', 'NoPow'
[py3.7]> pow(ni_pow, child_ni, ni_pow)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for pow(): 'NotImplementedPow',
'ChildOfNotImplementedPow', 'NotImplementedPow'

[py3.7]> # And none of them work when given as the modulus either
... pow(no_pow, no_pow, has_pow)
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: unsupported operand type(s) for pow(): 'NoPow', 'NoPow',
'UnrelatedPow'
[py3.7]> pow(ni_pow, ni_pow, has_pow)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for pow(): 'NotImplementedPow',
'NotImplementedPow', 'UnrelatedPow'
[py3.7]> pow(no_pow, no_pow, child_no)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for pow(): 'NoPow', 'NoPow',
'ChildOfNoPow'
[py3.7]> pow(ni_pow, ni_pow, child_ni)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for pow(): 'NotImplementedPow',
'NotImplementedPow', 'ChildOfNotImplementedPow'

[1] https://bugs.python.org/issue39302

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/DT4AJNAYHNAD6UL2SPFVAVXKMD666LS3/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to