2012-12-06 36 views
6

我想比较一对字典,并使用'模糊'浮点比较或更好的方式使用numpy.allclose()来做到这一点。但是,在Python中使用默认==!=来执行此操作不会执行此操作。比较包含浮点值的Python字典

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

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

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

我想用!=这两种类型的字典进行比较,并使其返回True如果唯一的不同之处内的浮点数一定范围内。例如,如果关闭(不确定我想要的精度),请不要计算不同的值。

我想我可以递归地通过自己手工检查字典,只需使用numpy.allclose()作为浮点值并回退到所有其他类型的正常相等性测试等等。但是,这有点棘手并且容易出错。我认为这将是一个可以接受的解决方案,我很乐意看到喜欢它的人。希望有更优雅的东西。

我头上的优雅解决方案看起来像下面这样。但是,我不知道这样的事情,甚至有可能:

with hacked_float_compare: 
    result = d1 != d2 

因此,这种情况下管理者内,我将代替浮点比较(只用于要么我自己的比较或numpy.allclose()标准float()值。

同样,我不知道这是可能的,因为猴子修补float()真的不能这样做,因为它是写在C。我也想避免在http://stardict.sourceforge.net/Dictionaries.php下载每一个浮点值更改为我自己浮点类有一个__eq__()也许这是最好 w唉虽然?

+0

一个选项是为float创建一个包装,并在那里覆盖'__eq__'。 – NullUserException

+0

但是你需要用'fuzzyfloat(0.5)'等来创建所有的浮点数。 – alexis

+0

对。我知道这种方法的工作原理,只是不想使用特殊的对象/类,如果我能避免它。在这个例子中,我只需要比较“模糊”。这就是为什么我希望使用上下文管理器,并在有限的时间内进入不同的“模式”。 –

回答

5

避免子类化内置类型。当你发现你的对象由于某种不明原因而改变了类型时,你会后悔的。改用代表团。例如:

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

总之,子类化内置的结果比开始时想象的要多得多,它可能会引入一些不可预知的错误,这是由于您未预料到的类型或行为的改变。调试也变得更加困难,因为您不能简单地在日志中打印对象的实例,表示将是正确的!你真的必须检查周围所有对象的类来捕捉这些微妙的错误。

在您的具体情况中,如果您打算仅在单一方法内转换字典,那么您可以避免dict的子类化的大多数缺点,但在那一点上,为什么不简单地编写函数并比较dict s用它? 这应该工作得很好,除非你想将dicts传递给进行比较的库函数。

+1

这看起来不错。然而,就我而言,我认为只是继承'dict'可能没问题。我只是想在本地转换字典来做这个比较。所以,这个新类只能在内部用于单一方法。这是否合理? –

+0

但是,如果'other'字典中的键不在第一个字典中,则此解决方案不会返回False。所以这个解决方案改变了比较字典的语义不仅仅是浮点比较,对吗? –

+0

@ durden2.0我从第一次发布它的时候就改变了一些,我认为它没问题。在我第一次检查'sorted(self)== sorted(other)'之前,我就读到了这个区别,但我认为上面也没关系。因为如果键的数量不同,那么通过比较长度来捕获它,然后我检查'self'中的每个键,并且如果它不在'other'中,将引发一个'KeyError' 'Exception Exception'正确地返回'False',所以它应该没问题。 无论如何,如果保证只是在一种方法的变化可能子类'dict'没关系。 – Bakuriu

1

要覆盖比较运算符,您需要定义使用不同运算符的派生类。所以你不能按照你的建议去做。你可以做的是从dict得出一个“模糊浮动”类(如@null)建议,或派生和类,并指定它使用的模糊浮动比较:

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

我应该然而补充一点,你冒着不确定性:你的词典会比较平等的,但包含略微不同的数字,因此subsquent计算可以给你的结果做比较相等,这取决于词典中,你使用。在我看来,一个更安全(更明智)的方法是将你的花车插入字典时将它们四舍五入,以便比较严格相等。

+0

是的,这也会起作用。然而,我并没有看到自己的dict类并将比较代码放在dict的__eq__中。这个完全相同的代码可能只是一个需要两个字节的方法。然后,我不必在任何地方使用这种新字典或转换现有的字典等等。再次说明,如果我在很多地方这样做,这种解决方案将会很好。然而,这只是一个比较重要的领域。 –

+0

此外,使用这些浮点数进行计算是非常棘手的,只是因为浮点表示等等。另外,在我的场景中,我真的不介意这些数字在彼此的范围内。这不会导致任何奇怪的行动。这只是我想要应用的一个非常有限的代码区域。 –

+0

Python的字典比较是递归的。如果你派生一个类,python将处理递归,你只需要实现平坦的逻辑:检查丢失或额外的键,并比较值。 – alexis

2

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

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