Permalink
Cannot retrieve contributors at this time
189 lines (142 sloc)
6.5 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
peps/pep-0535.txt
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains 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
| PEP: 535 | |
| Title: Rich comparison chaining | |
| Version: $Revision$ | |
| Last-Modified: $Date$ | |
| Author: Nick Coghlan <ncoghlan@gmail.com> | |
| Status: Deferred | |
| Type: Standards Track | |
| Content-Type: text/x-rst | |
| Requires: 532 | |
| Created: 12-Nov-2016 | |
| Python-Version: 3.8 | |
| PEP Deferral | |
| ============ | |
| Further consideration of this PEP has been deferred until Python 3.8 at the | |
| earliest. | |
| 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 | |
| (https://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/ |