-3

以下比较关系(=、≠、<、>、≤ 和 ≥)之间的数学关系始终有效,因此在 Python 中默认实现(除了 2 个联合关系,这似乎是任意的,这也是本文的原因):

  • 2互补关系:“=和≠互为互补”;
  • 6个逆向关系*:“=是自身的逆向关系”、“≠是自身的逆向关系”、“<和>是彼此的逆向关系”、“≤和≥是彼此的逆向关系”;
  • 2个并集关系:“≤是<和=的并集,”≥是>和=的并集。

以下比较关系之间的关系仅对总订单functools.total_ordering有效,因此在 Python 中默认不实现(但用户可以通过 Python 标准库提供的类装饰器方便地实现它们):

  • 4种互补关系:“<和≥互为补”,">和≤互为补”。

为什么Python只缺少上面的2个联合关系(“≤是联合<和=”,“≥是>和=”的联合)?

它应该提供一个__le__关于and 的默认实现,以及一个关于__lt__and的默认实现,就像这些(但可能在 C 中为了性能,比如):__eq____ge____gt____eq____ne__

def __le__(self, other):
    result_1 = self.__lt__(other)
    result_2 = self.__eq__(other)
    if result_1 is not NotImplemented and result_2 is not NotImplemented:
        return result_1 or result_2
    return NotImplemented

def __ge__(self, other):
    result_1 = self.__gt__(other)
    result_2 = self.__eq__(other)
    if result_1 is not NotImplemented and result_2 is not NotImplemented:
        return result_1 or result_2
    return NotImplemented

2 个联合关系始终有效,因此这些默认实现将使用户不​​必一直提供它们(如这里)。

这是Python 文档的段落,其中明确指出默认情况下当前未实现 2 个联合关系(粗体强调我的):

默认情况下,__ne__()委托__eq__()并反转结果,除非它是NotImplemented. 比较运算符之间没有其他隐含关系,例如,真值(x<y or x==y)不隐含x<=y


* 逆向关系通过NotImplemented协议在 Python 中实现。

4

3 回答 3

6

为什么做出这个决定只有原作者才知道,但根据手册中的这些提示,可以推断出原因:

要从单个根操作自动生成排序操作,请参阅functools.total_ordering()

虽然这个装饰器可以很容易地创建表现良好的完全有序类型,但它确实以派生比较方法的执行速度较慢和更复杂的堆栈跟踪为代价。如果性能基准测试表明这是给定应用程序的瓶颈,则实施所有六种丰富的比较方法可能会提供简单的速度提升。

将此与 Python 的“显式优于隐式”的口头禅相结合,以下推理应该是令人满意的:

派生实际上是免费的,它只是操作__ne__,即反转布尔值。__eq__not o.__eq__(other)

__le__但是,从 和 的并集__lt__派生意味着__eq__需要调用这两种方法,如果完成的比较足够复杂,尤其是与优化的单个__le__实现相比,这可能会对性能造成很大影响。Python 允许您通过使用total_ordering装饰器显式地选择这种便利而不是性能,但它不会隐式地强加给您。

如果您尝试进行未实现的比较而不是隐式派生比较,您也可能会争论显式错误,而隐式派生比较可能会产生细微的错误,具体取决于您自定义类的用途。Python 不会在这里为您做任何猜测,而是让您自己明确地实现您想要的比较,或者再次明确地选择加入派生比较。

于 2020-03-05T09:34:55.720 回答
5

TLDR:比较运算符不需要返回bool。这意味着结果可能不严格遵守“a <= ba < b or a == b”或类似的关系。最重要的是,布尔值or可能无法保留其语义。

自动生成特殊方法可能会默默地导致错误行为,类似于自动__bool__不普遍适用的方式。(这个例子也将<=etc. 视为多于bool。)


一个例子是通过比较运算符表示时间点。例如,usim模拟框架(免责声明:我维护这个包)定义了可以检查等待的时间点。我们可以使用比较来描述某个时间点的“在或之后”:

  • time > 20002000年后。
  • time == 2000在 2000 年。
  • time >= 20002000 年或之后。

(同样适用于<and ==,但限制更难解释。)

值得注意的是,每个表达式都有两个特征:现在是否满足bool(time >= 2000))和何时满足await (time >= 2000))。第一个显然可以针对每种情况进行评估。但是,第二个不能。

等待==并且>=可以通过等待/睡眠直到一个确切的时间点来完成。然而,等待>需要等待一个时间点加上一些无限小的延迟。后者无法准确表达,因为当代数字类型没有通用的无限小但非零的数字。

因此,==和的结果>=基本上不同于>. 推导>=为“ > or ==”是错误的。因此,定义usim.time但不是为了避免错误。自动定义比较运算符会阻止这种情况,或者错误地定义运算符。==>=>

于 2020-03-05T09:38:51.367 回答
5
于 2020-09-28T13:03:57.423 回答