我想比较一对字典并使用“模糊”浮点比较,或者更好地使用numpy.allclose()
来进行比较。但是,在Python中使用Python中的默认==
或!=
不会执行此操作。
我想知道是否有办法改变浮点比较操作(可能使用上下文管理器进行安全清理)。
我相信一个例子会有所帮助。我有一个深度嵌套的dict,其中包含各种值。其中一些值是浮点值。我知道“比较”浮点值等有很多陷阱。
d1 = {'a': {'b': 1.123456}}
d2 = {'a': {'b': 1.1234578}}
我想使用!=
比较这两个词组,如果唯一的差异是某个范围内的浮点数,则返回True
。例如,如果关闭(不确定我想要的精度),请不要计算不同的值。
我想我可以自己递归地查看dicts并手动使用numpy.allclose()
获取浮点值并回退到所有其他类型的正常相等测试等。但是,这有点棘手和错误易于。我认为这是一个可以接受的解决方案,我很乐意看到一个喜欢它的人。希望有更优雅的东西。
我头脑中的优雅解决方案看起来如下所示。但是,我不知道是否有这样的事情是可能的:
with hacked_float_compare:
result = d1 != d2
因此,在这个上下文管理器中,我将替换浮点比较(仅用于标准float()
值与我自己的比较或numpy.allclose()
。
同样,我不确定这是否可行,因为猴子修补float()
无法真正完成,因为它是用C
编写的。我还想避免将dicts中的每个浮点值更改为我自己的具有__eq__()
的float类。也许这是最好的方式?
答案 0 :(得分:6)
避免继承内置类型。当你发现你的对象因某种未知原因而改变了类型时,你会后悔的。改为使用委托。例如:
import operator as op
class FuzzyDict(object):
def __init__(self, iterable=(), float_eq=op.eq):
self._float_eq = float_eq
self._dict = dict(iterable)
def __getitem__(self, key):
return self._dict[key]
def __setitem__(self, key, val):
self._dict[key] = val
def __iter__(self):
return iter(self._dict)
def __len__(self):
return len(self._dict)
def __contains__(self, key):
return key in self._dict
def __eq__(self, other):
def compare(a, b):
if isinstance(a, float) and isinstance(b, float):
return self._float_eq(a, b)
else:
return a == b
try:
if len(self) != len(other):
return False
for key in self:
if not compare(self[key], other[key]):
return False
return True
except Exception:
return False
def __getattr__(self, attr):
# free features borrowed from dict
attr_val = getattr(self._dict, attr)
if callable(attr_val):
def wrapper(*args, **kwargs):
result = attr_val(*args, **kwargs)
if isinstance(result, dict):
return FuzzyDict(result, self._float_eq)
return result
return wrapper
return attr_val
一个示例用法:
>>> def float_eq(a, b):
... return abs(a - b) < 0.01
...
>>> A = FuzzyDict(float_eq=float_eq)
>>> B = FuzzyDict(float_eq=float_eq)
>>> A['a'] = 2.345
>>> A['b'] = 'a string'
>>> B['a'] = 2.345
>>> B['b'] = 'a string'
>>> B['a'] = 2.3445
>>> A == B
True
>>> B['a'] = 234.55
>>> A == B
False
>>> B['a'] = 2.345
>>> B['b'] = 'a strin'
>>> A == B
False
即使嵌套也可以工作:
>>> A['nested'] = FuzzyDict(float_eq=float_eq)
>>> A['nested']['a'] = 17.32
>>> B['nested'] = FuzzyDict(float_eq=float_eq)
>>> B['nested']['a'] = 17.321
>>> B['b'] = 'a string' # changed before
>>> A == B
True
>>> B['nested']['a'] = 17.34
>>> A == B
False
dict
的完全替代需要更多代码,可能需要进行一些测试以确定其有多强大,但即使上述解决方案也提供了许多dict
功能(例如copy
},setdefault
,get
,update
等。)
关于你不应该为内置子类化的原因。
此解决方案似乎简单而正确,但通常不是。 首先,即使您可以创建内置类型的子类,但这并不意味着它们被编写为用作子类,因此您可能会发现要使某些内容工作,您必须编写比您想象的更多的代码。
此外,您可能希望使用内置方法,但这些方法将返回内置类型的实例而不是类的实例,这意味着您必须重新实现每个方法类型。此外,您有时必须实现内置中未实现的其他方法。
例如,您可能会认为是list
的子类,因为list
只实现了__iadd__
和__add__
,您可以安全地重新实现这两种方法,但是你错了!您还必须实现__radd__
,否则表达式如下:
[1,2,3] + MyList([1,2,3])
会返回正常的list
而非MyList
。
总之,对内置子类进行子类化的后果比您在开始时的想法要多得多,并且由于您没有预料到的类型或行为的更改,它可能会引入一些不可预测的错误。调试也变得更难,因为你不能简单地在日志中打印对象的实例,表示是正确的!你真的必须检查周围所有对象的类来捕捉这些微妙的错误。
在您的特定情况下,如果您计划仅在单个方法中转换字典,那么您可以避免子类化dict
的大多数缺点,但在那时为什么不简单地编写函数并进行比较dict
用它?
除非你想将dict
传递给进行比较的库函数,否则这应该可以正常工作。
答案 1 :(得分:2)
仅供参考,我认为在我的情况下,子类化并不是最好的方法。我已经制定了一个我最有可能使用的解决方案here。
这不是公认的答案,因为它是基于我从这个主题中学到的东西的协作方法。只是想要一个其他人可以从中受益的“解决方案”。
答案 2 :(得分:1)
要覆盖比较运算符,您需要定义使用不同运算符的派生类。所以你不能按照你的建议去做。你可以做的是派生一个“模糊浮点”类(如@Null)建议,或从dict
派生和类,并指定它在浮点数上使用模糊比较:
class fuzzydict(dict):
def __eq__(self, other):
"""Manually compare each element of `self` with `other`.
Float values are compared up to reasonable precision."""
你必须自己完成字典比较的逻辑,它可能不会像内置的比较那么快,但你可以在代码中编写dict1 == dict2
。只需确保对可能包含浮点数的所有(嵌套)词典使用fuzzydict
而不是dict
。