内置容器的自定义比较
在我的代码中,有很多地方需要比较不同容器(比如列表、字典等)是否相等。这些容器里的键和值的类型有浮点数、布尔值、整数和字符串。使用内置的 == 和 != 比较运算符工作得很好。
我刚刚了解到,容器中用到的浮点数需要用一个自定义的比较函数来进行比较。我已经写好了这个函数(我们称它为 approxEqual(),假设它接受两个浮点数,如果判断它们相等就返回 True,否则返回 False)。
我希望对现有代码的改动尽量少。(新的类、函数等可以复杂一些。)
举个例子:
if dict1 != dict2:
raise DataMismatch
需要重写 dict1 != dict2
这个条件,使得 dict1 和 dict2 中的浮点数值是通过 approxEqual 函数来比较,而不是用 __eq__
。
字典的实际内容来自不同的来源(比如解析文件、计算等)。
注意:我之前 问过一个问题,关于如何重写内置浮点数的 eq。那本来是个简单的解决办法,但我了解到 Python 不允许重写内置类型的 __eq__
运算符。所以我才有了这个新问题。
1 个回答
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
是一个接受两个值并返回 True
或 False
的函数,就像 ==
一样。这样做可能会对你指定的标准造成太大的影响。
如果你可以改变容器本身(也就是说,创建容器对象的地方远少于检查两个容器相等的地方),那么使用一个重写了 __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)
?这个传递性条件并不适用于我见过的大多数“模糊比较”,而这对于基于哈希表的容器(如字典和集合)来说是完全不可或缺的。如果你能跨过这个障碍,那么让哈希值以某种方式“神奇地”保持一致的问题就会出现——如果一个字典中的两个实际上不同的键在这个意义上“映射到”另一个字典中的同一个键,那么应该使用哪一个对应的值呢...? 如果你问我,这样的思路会让人发疯,所以我希望当你说值时,你确实是指值,而不是键!-)