7

我想比较一对字典并使用“模糊”浮点比较或更好的方法numpy.allclose()。但是,使用默认值==!=在 Python 中用于 dicts 不会这样做。

我想知道是否有办法改变浮点比较操作(可能使用上下文管理器进行安全清理)。

我相信一个例子在这里会有所帮助。我有一个包含各种值的深度嵌套的字典。其中一些值是浮点值。我知道“比较”浮点值等有很多陷阱。

d1 = {'a': {'b': 1.123456}}
d2 = {'a': {'b': 1.1234578}}

如果唯一的区别是一定范围内的浮点数,我想!=用来比较这两个字典并让它返回。True例如,如果接近,则不要计算不同的值(不确定我想要的精度)。

我想我可以自己递归地遍历字典并手动仅numpy.allclose()用于浮点值并回退到所有其他类型的正常相等测试等。但是,这有点棘手且容易出错。我确实认为这将是一个可以接受的解决方案,我很想看到一个这样的解决方案。希望有更优雅的东西。

我脑海中的优雅解决方案如下所示。但是,我不知道这样的事情是否可能:

with hacked_float_compare:
    result = d1 != d2

因此,在这个上下文管理器中,我将替换浮点比较(仅用于标准float()值,用我自己的比较或numpy.allclose().

同样,我不确定这是可能的,因为猴子补丁float()不能真正完成,因为它是用C. 我还想避免将 dicts 中的每个浮点值更改为我自己的具有__eq__(). 也许这是最好的方法?

4

3 回答 3

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,、、setdefaultgetupdate


关于为什么你不应该继承一个内置的。

这个解决方案看起来简单而正确,但通常并非如此。首先,即使您可以对内置类型进行子类化,但这并不意味着它们被编写为用作子类,因此您可能会发现要使某些东西起作用,您必须编写比您想象的更多的代码。

此外,您可能希望使用内置方法,但这些方法将返回内置类型的实例而不是您的类的实例,这意味着您必须重新实现该类型的每个方法。此外,您有时必须实现内置未实现的其他方法。

例如,子类化list你可能会这样认为,因为list只实现了__iadd____add__你可以安全地重新实现这两个方法,但你错了!您还必须实现__radd__,否则表达式如下:

[1,2,3] + MyList([1,2,3])

将返回正常list而不是MyList.

总而言之,子类化一个内置函数比你一开始想的要多得多,并且由于类型或行为的变化,它可能会引入一些你没有预料到的不可预知的错误。调试也变得更加困难,因为您不能简单地在日志中打印对象的实例,表示是正确的!您确实必须检查周围所有对象的类以捕获这些细微的错误。

在您的特定情况下,如果您打算仅在单个方法内转换字典,那么您可能会避免 subclassing 的大多数缺点dict,但是在这一点上,您为什么不简单地编写一个函数并将dicts 与它进行比较呢?这应该很好用,除非您想将dicts 传递给进行比较的库函数。

于 2012-12-06T20:02:38.907 回答
3

仅供参考,我认为在我的情况下,子类化并不是最好的方法。我已经制定了一个我很可能会在这里使用的解决方案。

这不是公认的答案,因为它是基于我从该线程中学到的协作方法。只是想要一个其他人可以从中受益的“解决方案”。

于 2012-12-07T21:13:16.527 回答
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所有可能包含浮点数的(嵌套)字典。

但是,我应该补充一点,您冒着不确定性的风险:您的字典将比较相等但包含略有不同的数字,因此后续计算可能会给您提供相等的结果,具体取决于您使用的字典。在我看来,更安全(和更理智)的方法是在将浮点数插入字典时对其进行舍入,以便它们比较严格相等。

于 2012-12-06T18:05:24.937 回答