PEP 532 revision and restructure (#156)
- Language level inconsistencies eliminated by proposing both `if` and `else` as circuit-breaking operators - PEP 532 no longer competes with PEP 505 at all - Rich comparison chaining split out as PEP 535 - Mark E. Haase is now listed as a co-author (as I was better able to integrate some of his earlier suggestions) - acknowledged key feedback on the initial draft
This commit is contained in:
parent
77122d16c9
commit
80f963d066
1146
pep-0532.txt
1146
pep-0532.txt
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 9.1 KiB |
|
@ -0,0 +1,192 @@
|
|||
PEP: 535
|
||||
Title: Rich comparison chaining
|
||||
Version: $Revision$
|
||||
Last-Modified: $Date$
|
||||
Author: Nick Coghlan <ncoghlan@gmail.com>
|
||||
Status: Draft
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Requires: 532
|
||||
Created: 12-Nov-2016
|
||||
Python-Version: 3.7
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
Inspired by PEP 335, and building on the circuit breaking protocol described
|
||||
in PEP 532, this PEP proposes a change to the definition of chained comparisons,
|
||||
where the comparison chaining will be updated to use the left-associative
|
||||
circuit breaking operator (``else``) rather than the logical disjunction
|
||||
operator (``and``) if the left hand comparison returns a circuit breaker as
|
||||
its result.
|
||||
|
||||
While there are some practical complexities arising from the current handling
|
||||
of single-valued arrays in NumPy, this change should be sufficient to allow
|
||||
elementwise chained comparison operations for matrices, where the result
|
||||
is a matrix of boolean values, rather than raising ``ValueError``
|
||||
or tautologically returning ``True`` (indicating a non-empty matrix).
|
||||
|
||||
|
||||
Relationship with other PEPs
|
||||
============================
|
||||
|
||||
This PEP has been extracted from earlier iterations of PEP 532, as a
|
||||
follow-on use case for the circuit breaking protocol, rather than an essential
|
||||
part of its introduction.
|
||||
|
||||
The specific proposal in this PEP to handle the element-wise comparison use
|
||||
case by changing the semantic definition of comparison chaining is drawn
|
||||
directly from Guido's rejection of PEP 335.
|
||||
|
||||
|
||||
Specification
|
||||
=============
|
||||
|
||||
A chained comparison like ``0 < x < 10`` written as::
|
||||
|
||||
LEFT_BOUND LEFT_OP EXPR RIGHT_OP RIGHT_BOUND
|
||||
|
||||
is currently roughly semantically equivalent to::
|
||||
|
||||
_expr = EXPR
|
||||
_lhs_result = LEFT_BOUND LEFT_OP _expr
|
||||
_expr_result = _lhs_result and (_expr RIGHT_OP RIGHT_BOUND)
|
||||
|
||||
Using the circuit breaking concepts introduced in PEP 532, this PEP proposes
|
||||
that comparison chaining be changed to explicitly check if the left comparison
|
||||
returns a circuit breaker, and if so, use ``else`` rather than ``and`` to
|
||||
implement the comparison chaining::
|
||||
|
||||
_expr = EXPR
|
||||
_lhs_result = LEFT_BOUND LEFT_OP _expr
|
||||
if hasattr(type(_lhs_result), "__else__"):
|
||||
_expr_result = _lhs_result else (_expr RIGHT_OP RIGHT_BOUND)
|
||||
else:
|
||||
_expr_result = _lhs_result and (_expr RIGHT_OP RIGHT_BOUND)
|
||||
|
||||
This allows types like NumPy arrays to control the behaviour of chained
|
||||
comparisons by returning suitably defined circuit breakers from comparison
|
||||
operations.
|
||||
|
||||
The expansion of this logic to an arbitrary number of chained comparison
|
||||
operations would be the same as the existing expansion for ``and``.
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
In ultimately rejecting PEP 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 work
|
||||
with NumPy arrays::
|
||||
|
||||
>>> import numpy as np
|
||||
>>> increasing = np.arange(5)
|
||||
>>> increasing
|
||||
array([0, 1, 2, 3, 4])
|
||||
>>> decreasing = np.arange(4, -1, -1)
|
||||
>>> decreasing
|
||||
array([4, 3, 2, 1, 0])
|
||||
>>> increasing < decreasing
|
||||
array([ True, True, False, False, False], dtype=bool)
|
||||
|
||||
Here we see that NumPy array comparisons are element-wise by default, comparing
|
||||
each element in the left hand array to the corresponding element in the right
|
||||
hand array, and producing a matrix of boolean results.
|
||||
|
||||
If either side of the comparison is a scalar value, then it is broadcast across
|
||||
the array and compared to each individual element::
|
||||
|
||||
>>> 0 < increasing
|
||||
array([False, True, True, True, True], dtype=bool)
|
||||
>>> increasing < 4
|
||||
array([ True, True, True, True, False], dtype=bool)
|
||||
|
||||
However, this broadcasting idiom breaks down if we attempt to use chained
|
||||
comparisons::
|
||||
|
||||
>>> 0 < increasing < 4
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, 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 chained
|
||||
comparison into the form::
|
||||
|
||||
>>> 0 < increasing and increasing < 4
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, 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-element
|
||||
arrays where ``a.any()`` and ``a.all()`` can be assured of having the same
|
||||
result::
|
||||
|
||||
>>> np.array([False]) and np.array([False])
|
||||
array([False], dtype=bool)
|
||||
>>> np.array([False]) and np.array([True])
|
||||
array([False], dtype=bool)
|
||||
>>> np.array([True]) and np.array([False])
|
||||
array([False], dtype=bool)
|
||||
>>> np.array([True]) and np.array([True])
|
||||
array([ True], dtype=bool)
|
||||
|
||||
The proposal in this PEP would allow this situation to be changed by updating
|
||||
the definition of element-wise comparison operations in NumPy to return a
|
||||
dedicated subclass that implements the new circuit breaking protocol and also
|
||||
changes the result array's interpretation in a boolean context to always
|
||||
return ``False`` and hence never trigger the short-circuiting behaviour::
|
||||
|
||||
class ComparisonResultArray(np.ndarray):
|
||||
def __bool__(self):
|
||||
# Element-wise comparison chaining never short-circuits
|
||||
return False
|
||||
def _raise_NotImplementedError(self):
|
||||
msg = ("Comparison array truth values are ambiguous outside "
|
||||
"chained comparisons. Use a.any() or a.all()")
|
||||
raise NotImplementedError(msg)
|
||||
def __not__(self):
|
||||
self._raise_NotImplementedError()
|
||||
def __then__(self, result):
|
||||
self._raise_NotImplementedError()
|
||||
def __else__(self, result):
|
||||
return np.logical_and(self, other.view(ComparisonResultArray))
|
||||
|
||||
With this change, the chained comparison example above would be able to return::
|
||||
|
||||
>>> 0 < increasing < 4
|
||||
ComparisonResultArray([ False, True, True, True, False], dtype=bool)
|
||||
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
Actual implementation has been deferred pending in-principle interest in the
|
||||
idea of making the changes proposed in PEP 532.
|
||||
|
||||
...TBD...
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] PEP 335 rejection notification
|
||||
(http://mail.python.org/pipermail/python-dev/2012-March/117510.html)
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain under the terms of the
|
||||
CC0 1.0 license: https://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 70
|
||||
coding: utf-8
|
||||
End:
|
Loading…
Reference in New Issue