PEP 535 – 丰富比较链
- 作者:
- Alyssa Coghlan <ncoghlan at gmail.com>
- 状态:
- 推迟
- 类型:
- 标准跟踪
- 要求:
- 532
- 创建日期:
- 2016年11月12日
- Python 版本:
- 3.8
PEP 延期
对本 PEP 的进一步审议已推迟到 Python 3.8 或更晚。
摘要
受 PEP 335 的启发,并基于 PEP 532 中描述的熔断协议,本 PEP 提出了对链式比较定义的更改,即如果左侧比较返回熔断器作为其结果,则比较链将更新为使用左关联熔断运算符(else)而不是逻辑析取运算符(and)。
尽管 NumPy 中单值数组的当前处理方式存在一些实际复杂性,但此更改应足以允许矩阵的元素级链式比较操作,其中结果是布尔值矩阵,而不是引发 ValueError 或同义地返回 True(表示非空矩阵)。
与其他 PEP 的关系
本 PEP 已从 PEP 532 的早期迭代中提取出来,作为熔断协议的后续用例,而不是其引入的必要部分。
本 PEP 中通过更改比较链的语义定义来处理元素级比较用例的具体提议直接来自于 Guido 对 PEP 335 的拒绝。
规范
像 0 < x < 10 这样的链式比较写为
LEFT_BOUND LEFT_OP EXPR RIGHT_OP RIGHT_BOUND
目前大致在语义上等同于
_expr = EXPR
_lhs_result = LEFT_BOUND LEFT_OP _expr
_expr_result = _lhs_result and (_expr RIGHT_OP RIGHT_BOUND)
使用 PEP 532 中引入的熔断概念,本 PEP 建议更改比较链,以明确检查左侧比较是否返回熔断器,如果是,则使用 else 而不是 and 来实现比较链
_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)
这允许像 NumPy 数组这样的类型通过从比较操作返回适当定义的熔断器来控制链式比较的行为。
将此逻辑扩展到任意数量的链式比较操作将与 and 的现有扩展相同。
基本原理
在最终拒绝 PEP 335 时,Guido van Rossum 指出 [1]
NumPy 的人提出了一个有些不同的问题:对他们来说,最常见的用例是链式比较(例如 A < B < C)。
要理解这个观察结果,我们首先需要了解 NumPy 数组的比较方式
>>> 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)
在这里我们看到 NumPy 数组比较默认是元素级的,将左侧数组中的每个元素与右侧数组中的相应元素进行比较,并生成一个布尔结果矩阵。
如果比较的任何一侧是标量值,则它会在整个数组中广播,并与每个单独的元素进行比较
>>> 0 < increasing
array([False, True, True, True, True], dtype=bool)
>>> increasing < 4
array([ True, True, True, True, False], dtype=bool)
但是,如果我们尝试使用链式比较,这种广播习语就会失效
>>> 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()
问题是,在内部,Python 隐式地将此链式比较扩展为以下形式
>>> 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()
NumPy 只允许对单元素数组隐式强制转换为布尔值,其中可以确保 a.any() 和 a.all() 具有相同的结果
>>> 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)
本 PEP 中的提议将允许通过更新 NumPy 中元素级比较操作的定义来改变这种情况,使其返回一个专用的子类,该子类实现了新的熔断协议,并且还改变了结果数组在布尔上下文中的解释,使其始终返回 False,从而永远不会触发短路行为
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))
通过此更改,上述链式比较示例将能够返回
>>> 0 < increasing < 4
ComparisonResultArray([ False, True, True, True, False], dtype=bool)
实施
实际实施已推迟,以等待对 PEP 532 中提出的更改的想法的原则性兴趣。
……待定……
参考资料
版权
本文件已根据 CC0 1.0 许可协议的条款置于公共领域:https://creativecommons.org/publicdomain/zero/1.0/
来源: https://github.com/python/peps/blob/main/peps/pep-0535.rst
最后修改: 2025-02-01 08:59:27 GMT