2012-12-06 40 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

這不是公認的答案,因爲它是一種基於我從這個線索中學到的協作方法。只是想要一個「解決方案」,其他人可以從中受益。