Mag*_*ero -3 python relationship partial-ordering comparison-operators
比较关系(=、?、<、>、? 和 ?)之间的以下数学关系始终有效,因此在 Python 中默认实现(除了 2 个联合关系,这似乎是任意的,这也是本文的原因):
以下比较关系之间的关系仅对总订单有效,因此在Python中默认不实现(但用户可以functools.total_ordering通过Python标准库提供的类装饰器方便地实现它们):
为什么 Python 只缺少上面的 2 个联合关系(“?是联合 < 和 =" 和“?是 > 和 =" 的联合)?
它应该提供的默认实现__le__来讲__lt__和__eq__,和的默认实现__ge__来讲__gt__和__eq__(性能,但可能是在C,像,像这些__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
Run Code Online (Sandbox Code Playgroud)
这 2 个联合关系始终有效,因此这些默认实现将使用户不必一直提供它们(如此处)。
这是Python 文档的段落,其中明确指出当前默认情况下未实现 2 个联合关系(粗体强调我的):
默认情况下,
__ne__()将__eq__()结果委托给并反转结果,除非它是NotImplemented。比较运算符之间没有其他隐含的关系,例如,的真值(x<y or x==y)并不意味着x<=y。
* Converse 关系是通过NotImplemented协议在 Python 中实现的。
为什么做出这个决定只有原作者知道,但根据手册中的这些提示可以推断出原因:
要从单个根操作自动生成排序操作,请参阅
functools.total_ordering()。
虽然这个装饰器可以很容易地创建表现良好的完全有序类型,但它确实以执行速度较慢和派生比较方法的堆栈跟踪更复杂为代价。如果性能基准测试表明这是给定应用程序的瓶颈,那么实现所有六种丰富的比较方法可能会提供简单的速度提升。
将此与 Python 的口头禅相结合显式优于隐式,以下推理应该是令人满意的:
派生__ne__自__eq__实际上是免费的,它只是操作not o.__eq__(other),即反转布尔值。
但是,__le__从__lt__and 的并集派生__eq__意味着需要调用这两种方法,如果完成的比较足够复杂,这可能会对性能造成很大的影响,尤其是与优化的单个__le__实现相比。Python 允许您通过使用total_ordering装饰器显式地选择使用这种性能上的便利,但它不会隐式地将其强加给您。
如果您尝试进行未实现的比较而不是隐式派生的比较,而您没有实现并且可能会产生细微的错误,那么您也可以争论显式错误,这取决于您打算对自定义类做什么。Python 不会在这里为您做任何猜测,而是由您来明确地实现您想要的比较,或者再次明确地选择加入派生比较。
TLDR:比较运算符不需要返回bool。这意味着结果可能不会严格遵守“a <= b是a < b or a == b”或类似的关系。最重要的是,布尔值or可能无法保留其语义。
自动生成特殊方法可能会默默地导致错误的行为,类似于自动__bool__通常不适用的方式。(此示例还将<=etc. 视为不仅仅是bool.)
一个例子是通过比较运算符表达时间点。例如,usim模拟框架(免责声明:我维护这个包)定义了可以检查和等待的时间点。我们可以使用比较来描述某个时间点“在或之后”:
time > 2000 2000年后。time == 2000 在 2000 年。time >= 2000 2000 年或之后。 (同样适用于<和==,但限制更难解释。)
值得注意的是,每个表达式都有两个特征:现在是否满足( bool(time >= 2000)) 和何时满足( await (time >= 2000))。第一个显然可以针对每种情况进行评估。但是,第二个不能。
等待==和>=可以通过等待/睡眠直到一个确切的时间点来完成。然而,等待>需要等待一个时间点加上一些无限小的延迟。后者无法准确表达,因为当代数字类型没有通用的无限小但非零的数字。
因此,==和的结果>=从根本上不同于>。派生>=为“ > or ==”是错误的。因此,usim.time定义==和>=但不是>为了避免错误。自动定义比较运算符会阻止这种情况,或者错误地定义运算符。
你的问题是基于一些不正确的假设。你开始你的问题:
比较之间的关系下面的数学关系(
=,?,<,>,?和?)总是有效的,因此在默认情况下用Python实现(除2个联盟关系,这似乎是任意的,是这个职位的原因)。
目前因为没有默认实现<或>两种。没有默认__lt__或__gt__实现,所以不能成为一个默认的实现__le__或__ge__两种。*
这包含在值比较下的表达式参考文档中:
相等比较(
==和!=)的默认行为基于对象的身份。因此,相同身份的实例的相等比较导致相等,不同身份的实例的相等比较导致不平等。这种默认行为的动机是希望所有对象都应该是自反的(即x is y隐含x == y)。默认顺序的比较(
<,>,<=,和>=)不设置; 一次尝试引发TypeError。这种默认行为的动机是缺乏与平等类似的不变量。默认相等比较的行为,即具有不同身份的实例总是不相等的,可能与需要具有对象值和基于值的相等的合理定义的类型形成对比。这些类型需要自定义它们的比较行为,事实上,许多内置类型已经这样做了。
不提供默认行为的动机包含在文档中。请注意,这些比较是在每个对象的值之间进行的,这是一个抽象概念。从同一文档部分,在开始时:
对象的值在 Python 中是一个相当抽象的概念:例如,对象的值没有规范的访问方法。此外,不要求对象的值应以特定方式构造,例如由其所有数据属性组成。比较运算符实现了对象值是什么的特定概念。人们可以将它们视为通过比较实现间接定义对象的值。
因此,比较是在对象价值的一种概念之间进行的。但是这个概念究竟是什么,取决于开发人员来实现。Python不会对对象的值做任何假设。这包括假设对象值中存在任何固有的排序。
唯一的原因==是实现在所有的,是因为当x is y为真,则x和y是完全相同的对象,所以价值x和价值y是完全一样的事情,因此必须相等。Python 在很多不同的地方都依赖于相等性测试(例如针对列表的包含测试),因此没有默认的相等性概念会使 Python 中的很多事情变得更加困难。!=是 的直接逆==;如果==操作数的值相同时!=为真,则仅在==为假时为真。
你不能说同样的<,<=,=>并>没有从开发者的帮助,因为他们需要了解如何的对象需要一个值的抽象的概念进行比较,以其他类似的价值更多的信息。在这里,x <= y不仅仅是 的倒数x > y,因为没有关于xor的值的任何信息y,以及它与==or!=或<或任何其他值比较的关系。
您还声明:
这 2 个联合关系始终有效,因此这些默认实现将使用户不必一直提供它们
2 联合关系并不总是有效的。可能是>and<运算符实现没有进行比较,并且实现可以自由返回除Trueor之外的结果False。从关于__lt__等方法的文档中:
但是,这些方法可以返回任何值,因此如果在布尔上下文中使用比较运算符(例如,在 if 语句的条件中),Python 将对该值调用 bool() 以确定结果是 true 还是 false .
如果一个实现决定给两个对象之间的>和<一个完全不同的含义,开发人员不应该留下错误的默认实现__le__和__ge__假设实现 for__lt__和__gt__返回布尔值,因此将调用bool()它们的返回值。这可能是不可取的,开发者应该可以随意重载的意思__bool__呢!
对此的规范示例是 Numpy 库,它是实现这些丰富的比较挂钩的主要驱动程序。Numpy 数组不会为比较操作返回布尔值。相反,它们广播两个数组中所有包含的值之间的操作以生成一个新数组,因此array_a < array_b为来自array_a和 的每个配对值生成一个新的布尔值数组array_b。数组不是布尔值,您的默认实现会因bool(array)引发异常而中断。虽然在 Numpy 的情况下,它们也实现__le__并__ge__广播比较,但 Python 不能要求所有类型为这些钩子提供实现,只是在不需要时禁用它们。
您似乎将数学关系与 Python 对其中一些关系的使用混为一谈。数学关系适用于某些类别的值(主要是数字)。它们不适用于其他领域,由每种类型的实现来决定是否遵守这些数学关系。
最后,之间的互补关系<和>=之间,以及>和<=*只适用于总订单二元关系,如在规定补上维基百科文章的部分二元关系:
例如,
=and?是彼此的补码,就像?and?、?and?、and?和?and 一样,对于总订单,还有<and?和>and?。
Python 不能假设所有类型的实现都希望在它们的值之间创建全序关系。
set例如,标准库类型不支持集合之间的全序,set_a < set_b当set_a是较大set_b. 这意味着可以有set_c是 的子集,set_b但set_c不一定是 的子集或超集set_a。设置的比较也没有连通性,set_a <= set_b并且set_b <= set_a可以两者是假的,在同一时间,当这两组具有不存在于其他元素。
*注:object.__lt__,object.__gt__,object.__le__和object.__ge__方法确实有一个默认的实现,但只能返回NotImplemented无条件。他们的存在只是为了简化的实施<,>,<=和>=运营商,这对于a [operator] b需要测试a.__[hook]__(b),然后再尝试b.__[converse hook]__(a)如果第一个回报NotImplemented。如果没有默认实现,那么代码还需要首先检查钩子方法是否存在。尽管如此,使用<or>或<=or>=在确实提供自己实现的对象上会导致TypeError。难道不 将这些视为默认实现,它们不进行任何值比较。
| 归档时间: |
|
| 查看次数: |
566 次 |
| 最近记录: |