Further consideration of this PEP has been deferred until Python 3.8 at theearliest.
Inspired byPEP 335, and building on the circuit breaking protocol describedinPEP 532, this PEP proposes a change to the definition of chained comparisons,where the comparison chaining will be updated to use the left-associativecircuit breaking operator (else) rather than the logical disjunctionoperator (and) if the left hand comparison returns a circuit breaker asits result.
While there are some practical complexities arising from the current handlingof single-valued arrays in NumPy, this change should be sufficient to allowelementwise chained comparison operations for matrices, where the resultis a matrix of boolean values, rather than raisingValueErroror tautologically returningTrue (indicating a non-empty matrix).
This PEP has been extracted from earlier iterations ofPEP 532, as afollow-on use case for the circuit breaking protocol, rather than an essentialpart of its introduction.
The specific proposal in this PEP to handle the element-wise comparison usecase by changing the semantic definition of comparison chaining is drawndirectly from Guido’s rejection ofPEP 335.
A chained comparison like0<x<10 written as:
LEFT_BOUNDLEFT_OPEXPRRIGHT_OPRIGHT_BOUND
is currently roughly semantically equivalent to:
_expr=EXPR_lhs_result=LEFT_BOUNDLEFT_OP_expr_expr_result=_lhs_resultand(_exprRIGHT_OPRIGHT_BOUND)
Using the circuit breaking concepts introduced inPEP 532, this PEP proposesthat comparison chaining be changed to explicitly check if the left comparisonreturns a circuit breaker, and if so, useelse rather thanand toimplement the comparison chaining:
_expr=EXPR_lhs_result=LEFT_BOUNDLEFT_OP_exprifhasattr(type(_lhs_result),"__else__"):_expr_result=_lhs_resultelse(_exprRIGHT_OPRIGHT_BOUND)else:_expr_result=_lhs_resultand(_exprRIGHT_OPRIGHT_BOUND)
This allows types like NumPy arrays to control the behaviour of chainedcomparisons by returning suitably defined circuit breakers from comparisonoperations.
The expansion of this logic to an arbitrary number of chained comparisonoperations would be the same as the existing expansion forand.
In ultimately rejectingPEP 335, Guido van Rossum noted[1]:
The NumPy folks brought up a somewhat separate issue: for them,the most common use case is chained comparisons (e.g. A < B < C).
To understand this observation, we first need to look at how comparisons workwith NumPy arrays:
>>>importnumpyasnp>>>increasing=np.arange(5)>>>increasingarray([0, 1, 2, 3, 4])>>>decreasing=np.arange(4,-1,-1)>>>decreasingarray([4, 3, 2, 1, 0])>>>increasing<decreasingarray([ True, True, False, False, False], dtype=bool)
Here we see that NumPy array comparisons are element-wise by default, comparingeach element in the left hand array to the corresponding element in the righthand array, and producing a matrix of boolean results.
If either side of the comparison is a scalar value, then it is broadcast acrossthe array and compared to each individual element:
>>>0<increasingarray([False, True, True, True, True], dtype=bool)>>>increasing<4array([ True, True, True, True, False], dtype=bool)
However, this broadcasting idiom breaks down if we attempt to use chainedcomparisons:
>>>0<increasing<4Traceback (most recent call last): File"<stdin>", line1, in<module>ValueError:The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
The problem is that internally, Python implicitly expands this chainedcomparison into the form:
>>>0<increasingandincreasing<4Traceback (most recent call last): File"<stdin>", line1, in<module>ValueError:The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
And NumPy only permits implicit coercion to a boolean value for single-elementarrays wherea.any() anda.all() can be assured of having the sameresult:
>>>np.array([False])andnp.array([False])array([False], dtype=bool)>>>np.array([False])andnp.array([True])array([False], dtype=bool)>>>np.array([True])andnp.array([False])array([False], dtype=bool)>>>np.array([True])andnp.array([True])array([ True], dtype=bool)
The proposal in this PEP would allow this situation to be changed by updatingthe definition of element-wise comparison operations in NumPy to return adedicated subclass that implements the new circuit breaking protocol and alsochanges the result array’s interpretation in a boolean context to alwaysreturnFalse and hence never trigger the short-circuiting behaviour:
classComparisonResultArray(np.ndarray):def__bool__(self):# Element-wise comparison chaining never short-circuitsreturnFalsedef_raise_NotImplementedError(self):msg=("Comparison array truth values are ambiguous outside ""chained comparisons. Use a.any() or a.all()")raiseNotImplementedError(msg)def__not__(self):self._raise_NotImplementedError()def__then__(self,result):self._raise_NotImplementedError()def__else__(self,result):returnnp.logical_and(self,other.view(ComparisonResultArray))
With this change, the chained comparison example above would be able to return:
>>>0<increasing<4ComparisonResultArray([ False, True, True, True, False], dtype=bool)
Actual implementation has been deferred pending in-principle interest in theidea of making the changes proposed inPEP 532.
…TBD…
This document has been placed in the public domain under the terms of theCC0 1.0 license:https://creativecommons.org/publicdomain/zero/1.0/
Source:https://github.com/python/peps/blob/main/peps/pep-0535.rst
Last modified:2025-02-01 08:59:27 GMT