Python __eq__ 中是否需要传递性?
Is there a need for transitivity in Python __eq__?
我正在使用自定义 __eq__
实现自己的 class。我想 return True
对于数学意义上不“相等”但模糊“匹配”的事物。
但是,这会导致数学意义上的传递性丧失,即 a == b && b ==c
,而 a
可能不等于 c
。
问题:Python 是否依赖于 __eq__
是可传递的?我正在尝试做的事情是否会破坏事物,或者只要我自己小心不要假设传递性,就可以这样做吗?
用例
我想将电话号码相互匹配,而这些电话号码可能是国际格式,也可能仅供国内使用(未指定国家代码)。如果没有指定国家代码,我希望一个数字等于一个数字,但如果指定,它应该只等于具有相同国家代码的数字,或者没有一个。
所以:
- 当然,
+31 6 12345678
应该等于+31 6 12345678
,06 12345678
应该等于06 12345678
+31 6 12345678
应等于 06 12345678
(和 v.v。)
+49 6 12345678
应等于 06 12345678
(和 v.v。)
- 但是
+31 6 12345678
不应该等于+49 6 12345678
编辑:我不需要散列(因此不会实现它),所以至少让生活更轻松。
没有 MUST 而是 SHOULD 关系,用于比较与通常理解的关系一致。 Python 明确地不强制执行此操作,并且 float
是一种内置类型,由于 float("nan")
.
而具有不同的行为
Expressions: Value comparisons
[…]
User-defined classes that customize their comparison behavior should follow some consistency rules, if possible:
- […]
- Comparison should be symmetric. In other words, the following expressions should have the same result:
x == y
and y == x
x != y
and y != x
x < y
and y > x
x <= y
and y >= x
- Comparison should be transitive. The following (non-exhaustive) examples illustrate that:
- x > y and y > z implies x > z
- x < y and y <= z implies x < z
Python does not enforce these consistency rules. In fact, the not-a-number values are an example for not following these rules.
不过,请记住,例外情况极其罕见,很容易被忽略:例如,大多数人会将 float
视为具有完全秩序。使用不常见的比较关系会严重增加维护工作量。
通过运算符对“模糊匹配”进行建模的规范方法是 subset、subsequence 或 containment 使用 非对称 运算符。
set
和frozenset
支持>
、>=
等表示一个集合包含另一个集合的所有值。
>>> a, b = {1, 5, 6, 8}, {5, 6}
>>> a >= a, a >= b, b >= a
(True, True, False)
-
str
和 bytes
支持 in
表示子序列被覆盖。
>>> a, b = "+31 6 12345678", "6 12345678"
>>> a in b, b in a
(False, True)
range
和 ipaddress
网络支持 in
来表示涵盖特定项目。
>>> IPv4Address('192.0.2.6') in IPv4Network('192.0.2.0/28')
True
值得注意的是,虽然这些运算符可能是可传递的,但它们不是对称的。例如,a >= b and c >= b
并不意味着 b >= c
,因此也不意味着 a >= c
,反之亦然。
实际上,可以将“没有国家代码的号码”建模为同一号码的“有国家代码的号码”的超集。这意味着 06 12345678 >= +31 6 12345678
和 06 12345678 >= +49 6 12345678
但反之则不然。为了进行对称比较,可以使用 a >= b or b >= a
而不是 a == b
。
__eq__
方法应该是可传递的;至少字典是这么认为的。
class A:
def __init__(self, name):
self.name = name
def __eq__(self, other):
for element in self.values:
if element is other:
return True
return False
def __hash__(self):
return 0
def __repr__(self):
return self.name
x, y, z = A('x'), A('y'), A('z')
x.values = [x,y]
y.values = [x,y,z]
z.values = [y,z]
print(x == y)
--> True
print (y == z)
--> True
print(x == z)
--> False
print({**{x:1},**{y:2, z: 3}})
--> {x: 3}
print({**{x:1},**{z:3, y:2}})
--> {x: 1, z: 2}
{**{x:1},**{y: 2, z:3}}
是两个字典的并集。没有人期望字典在更新后删除键。
print(z in {**{x:1},**{y:2, z: 3}})
--> False
通过更改联合中的顺序,您甚至可以获得不同大小的词典:
print(len({**{x:1},**{y:2, z: 3}}))
--> 1
print(len({**{x:1},**{z:3, y:2}}))
--> 2
我正在使用自定义 __eq__
实现自己的 class。我想 return True
对于数学意义上不“相等”但模糊“匹配”的事物。
但是,这会导致数学意义上的传递性丧失,即 a == b && b ==c
,而 a
可能不等于 c
。
问题:Python 是否依赖于 __eq__
是可传递的?我正在尝试做的事情是否会破坏事物,或者只要我自己小心不要假设传递性,就可以这样做吗?
用例
我想将电话号码相互匹配,而这些电话号码可能是国际格式,也可能仅供国内使用(未指定国家代码)。如果没有指定国家代码,我希望一个数字等于一个数字,但如果指定,它应该只等于具有相同国家代码的数字,或者没有一个。
所以:
- 当然,
+31 6 12345678
应该等于+31 6 12345678
,06 12345678
应该等于06 12345678
+31 6 12345678
应等于06 12345678
(和 v.v。)+49 6 12345678
应等于06 12345678
(和 v.v。)- 但是
+31 6 12345678
不应该等于+49 6 12345678
编辑:我不需要散列(因此不会实现它),所以至少让生活更轻松。
没有 MUST 而是 SHOULD 关系,用于比较与通常理解的关系一致。 Python 明确地不强制执行此操作,并且 float
是一种内置类型,由于 float("nan")
.
Expressions: Value comparisons
[…]
User-defined classes that customize their comparison behavior should follow some consistency rules, if possible:
- […]
- Comparison should be symmetric. In other words, the following expressions should have the same result:
x == y
andy == x
x != y
andy != x
x < y
andy > x
x <= y
andy >= x
- Comparison should be transitive. The following (non-exhaustive) examples illustrate that:
- x > y and y > z implies x > z
- x < y and y <= z implies x < z
Python does not enforce these consistency rules. In fact, the not-a-number values are an example for not following these rules.
不过,请记住,例外情况极其罕见,很容易被忽略:例如,大多数人会将 float
视为具有完全秩序。使用不常见的比较关系会严重增加维护工作量。
通过运算符对“模糊匹配”进行建模的规范方法是 subset、subsequence 或 containment 使用 非对称 运算符。
set
和frozenset
支持>
、>=
等表示一个集合包含另一个集合的所有值。>>> a, b = {1, 5, 6, 8}, {5, 6} >>> a >= a, a >= b, b >= a (True, True, False)
-
str
和bytes
支持in
表示子序列被覆盖。>>> a, b = "+31 6 12345678", "6 12345678" >>> a in b, b in a (False, True)
range
和ipaddress
网络支持in
来表示涵盖特定项目。>>> IPv4Address('192.0.2.6') in IPv4Network('192.0.2.0/28') True
值得注意的是,虽然这些运算符可能是可传递的,但它们不是对称的。例如,a >= b and c >= b
并不意味着 b >= c
,因此也不意味着 a >= c
,反之亦然。
实际上,可以将“没有国家代码的号码”建模为同一号码的“有国家代码的号码”的超集。这意味着 06 12345678 >= +31 6 12345678
和 06 12345678 >= +49 6 12345678
但反之则不然。为了进行对称比较,可以使用 a >= b or b >= a
而不是 a == b
。
__eq__
方法应该是可传递的;至少字典是这么认为的。
class A:
def __init__(self, name):
self.name = name
def __eq__(self, other):
for element in self.values:
if element is other:
return True
return False
def __hash__(self):
return 0
def __repr__(self):
return self.name
x, y, z = A('x'), A('y'), A('z')
x.values = [x,y]
y.values = [x,y,z]
z.values = [y,z]
print(x == y)
--> True
print (y == z)
--> True
print(x == z)
--> False
print({**{x:1},**{y:2, z: 3}})
--> {x: 3}
print({**{x:1},**{z:3, y:2}})
--> {x: 1, z: 2}
{**{x:1},**{y: 2, z:3}}
是两个字典的并集。没有人期望字典在更新后删除键。
print(z in {**{x:1},**{y:2, z: 3}})
--> False
通过更改联合中的顺序,您甚至可以获得不同大小的词典:
print(len({**{x:1},**{y:2, z: 3}}))
--> 1
print(len({**{x:1},**{z:3, y:2}}))
--> 2