内置容器的自定义比较

5 投票
1 回答
2705 浏览
提问于 2025-04-16 05:00

在我的代码中,有很多地方需要比较不同容器(比如列表、字典等)是否相等。这些容器里的键和值的类型有浮点数、布尔值、整数和字符串。使用内置的 == 和 != 比较运算符工作得很好。

我刚刚了解到,容器中用到的浮点数需要用一个自定义的比较函数来进行比较。我已经写好了这个函数(我们称它为 approxEqual(),假设它接受两个浮点数,如果判断它们相等就返回 True,否则返回 False)。

我希望对现有代码的改动尽量少。(新的类、函数等可以复杂一些。)

举个例子:

if dict1 != dict2:
  raise DataMismatch

需要重写 dict1 != dict2 这个条件,使得 dict1 和 dict2 中的浮点数值是通过 approxEqual 函数来比较,而不是用 __eq__

字典的实际内容来自不同的来源(比如解析文件、计算等)。

注意:我之前 问过一个问题,关于如何重写内置浮点数的 eq。那本来是个简单的解决办法,但我了解到 Python 不允许重写内置类型的 __eq__ 运算符。所以我才有了这个新问题。

1 个回答

9
class EqM_list(EqMixin, list): pass

要改变内置容器检查相等的方式,唯一的方法就是让它们包含的是“包装过的”值,而不是“原始”的值。这些包装过的值是放在一个类里,这个类重写了 __eq____ne__ 方法。这样做是为了改变容器本身使用相等检查的方式,比如在使用 in 操作符时,右边的操作数是一个列表,或者在容器自己的方法中,比如它们自己的 __eq__ 方法(type(x).__eq__(y) 是 Python 内部处理 x == y 的典型方式)。

如果你想做的是执行你自己的相等检查(而不改变容器内部执行的检查),那么唯一的方法就是把每个 cont1 == cont2 改成(例如) same(cont1, cont2, value_same),其中 value_same 是一个接受两个值并返回 TrueFalse 的函数,就像 == 一样。这样做可能会对你指定的标准造成太大的影响。

如果你可以改变容器本身(也就是说,创建容器对象的地方远少于检查两个容器相等的地方),那么使用一个重写了 __eq__ 的容器子类是最好的选择。

例如:

class EqMixin(object):
  def __eq__(self, other):
    return same(cont1, cont2, value_same)

(这里的 same 就是我在 A 的第二段提到的)然后在你有的地方(例如)

x = list(someiter)

把它改成

x = EqM_list(someiter)

并确保也捕捉到其他创建列表对象的方式,比如把

x = [bah*2 for bah in buh]

替换为

x = EqM_list(bah*2 for bah in buh)

x = d.keys()

替换为

x = EqM_list(d.iterkeys())

等等。

是的,我知道,这真麻烦——但这是 Python 的一个核心原则(和实践;-)),内置类型(无论是容器还是像 float 这样的值类型)本身不能被改变。这和 Ruby 或 Javascript 的哲学大相径庭(我个人更喜欢这种方式,但我也明白有时候这可能会显得有限制!)。

编辑:提问者的具体请求似乎是(根据这个回答)“我该如何为各种容器类型实现 same”,而不是如何在不把 == 改成函数调用的情况下应用它。如果没错的话,那么(例如)在不使用迭代器的情况下,为简单起见:

def samelist(a, b, samevalue):
    if len(a) != len(b): return False
    return all(samevalue(x, y) for x, y in zip(a, b))

def samedict(a, b, samevalue):
    if set(a) != set(b): return False
    return all(samevalue(a[x], b[x]) for x in a))

请注意,这适用于,如请求的那样,而不是 。对字典的键(或集合的成员)进行“模糊”相等比较是一个真实的问题。这样来看:首先,你如何绝对保证 samevalue(a, b) 和 samevalue(b, c) 完全意味着并确保 samevalue(a, c)?这个传递性条件并不适用于我见过的大多数“模糊比较”,而这对于基于哈希表的容器(如字典和集合)来说是完全不可或缺的。如果你能跨过这个障碍,那么让哈希值以某种方式“神奇地”保持一致的问题就会出现——如果一个字典中的两个实际上不同的键在这个意义上“映射到”另一个字典中的同一个键,那么应该使用哪一个对应的值呢...? 如果你问我,这样的思路会让人发疯,所以我希望当你说时,你确实是指,而不是键!-)

撰写回答