Unwrap right hand proxy operand in pure Python binary operators - #357
Merged
Merged
Conversation
The C extension unwraps both operands of a binary operator before applying it to the wrapped objects, but the pure Python implementation only unwrapped self and passed a right hand proxy through to the wrapped object's operator method. This was hidden where the wrapped type returned NotImplemented, as the right hand proxy's reflected method then unwrapped the other side, but gave a different result to the C extension where the wrapped type raised TypeError instead, or where the right hand operand was a proxied subclass overriding the reflected method. The pure Python binary, reflected and in-place operators now unwrap a right hand operand which is a proxy, matching the C extension. Tests cover all affected operators against the result of the operation on the unwrapped objects. The ternary pow() known issue is updated as a proxy exponent now works alongside a proxy base in both implementations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The C extension unwraps both operands of a binary operator before applying
it to the wrapped objects, but the pure Python implementation only unwrapped
selfand passed a right hand proxy through to the wrapped object'soperator method:
This usually went unnoticed. The wrapped object's method returns
NotImplementedwhen handed a proxy, Python then tries the right handproxy's reflected method, and that unwraps the other side. The difference
is visible in two cases, both of which need a proxy on both sides:
TypeErrorfor an operand it does not recognise,instead of returning
NotImplemented. Python treats a raised exception asfinal and never tries the reflected method, so the right hand proxy is
never unwrapped.
type and overrides the reflected method. Python gives that method
priority, but only when it sees the real types.
P(Strict(1)) + P(Strict(2))TypeErrorStrict(3)P(Base()) + P(Sub())Base.__add__Sub.__radd__All 14 binary operators, their reflected forms and the 13 in-place forms
were affected.
Fix
The pure Python operator methods now unwrap a right hand operand which is
a proxy, via a
_unwrap_operand()helper mirroringwrapt_unwrap_operand()in the C extension. The result is the same asapplying the operator to the two wrapped objects in both implementations.
The real type is checked rather than
__class__, which the proxy reportsas that of the wrapped object.
Where the left hand operand is not a proxy, Python has already chosen the
method to call before any wrapt code runs, so
Base() + P(Sub())stillcannot give
Sub.__radd__in either implementation. That is documented inthe change note rather than changed.
Tests
tests/core/test_proxy_operands.pyadds 41 tests, one per affectedoperator, in three groups: a wrapped type that raises
TypeError, thein-place forms of the same with and without an
__i*__method on thewrapped type, and the subclass reflected priority case. Each derives its
expected value from the operation on the unwrapped objects. All 41 failed
with the pure Python implementation before the fix and passed with the C
extension.
just testpasses across the full matrix.Docs
A change note is added under 2.4.2. The "Ternary
pow()" known issue isupdated, as it stated the pure Python implementation accepts only a proxy
base. A proxy exponent alongside a proxy base now works in both
implementations. A proxy exponent with a plain base, and a proxy modulo,
still fail in both, and the entry now describes that shared behaviour.