Is anyone aware of any existing tests for ternary_op in abstract.c?
[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 | ncoghlan@gmail.com | Brisbane, Australia
participants (1)
-
Nick Coghlan