簡體   English   中英

將Python dicts與包含的浮點值進行比較

[英]Comparing Python dicts with floating point values included

我想比較一對字典並使用'模糊'浮點比較或更好地使用numpy.allclose()來做到這一點。 但是,在Python中使用默認值==!=來表示dicts不會這樣做。

我想知道是否有辦法改變浮點比較操作(可能使用上下文管理器進行安全清理)。

我相信一個例子會有所幫助。 我有一個深度嵌套的dict,其中包含各種值。 其中一些值是浮點值。 我知道“比較”浮點值有很多陷阱等。

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

我想使用!=比較這兩個dicts,如果唯一的差異是某個范圍內的浮點數,則返回True 例如,如果值接近 ,則不要計算不同的值(不確定我想要的精度)。

我想我可以自己遞歸地查看numpy.allclose()並手動使用numpy.allclose()獲取浮點值並回退到所有其他類型的正常相等測試等。但是,這有點棘手且容易出錯。 我認為這是一個可以接受的解決方案,我很樂意看到一個喜歡它的人。 希望有更優雅的東西。

我頭腦中的優雅解決方案看起來如下所示。 但是,我不知道這樣的事情是否可能:

with hacked_float_compare:
    result = d1 != d2

因此,在這個上下文管理器中,我將替換浮點比較(僅用於標准float()值與我自己的比較或numpy.allclose()

同樣,我不確定這是否可行,因為猴子修補float()實際上無法完成,因為它是用C編寫的。 我還想避免將dicts中的每個浮點值更改為我自己的具有__eq__()的float類。 也許這是最好的方法嗎?

避免繼承內置類型。 當你發現你的對象因某種未知原因而改變了類型時,你會后悔的。 改為使用委托。 例如:

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功能(例如copysetdefaultgetupdate等)。


關於為什么你不應該為內置子類化。

這個解決方案似乎簡單而正確,但通常不是。 首先,即使您可以創建內置類型的子類,但這並不意味着它們被編寫為用作子類,因此您可能會發現要使某些內容有效,您必須編寫比您想象的更多的代碼。

此外,您可能希望使用內置方法,但這些方法將返回內置類型的實例而不是類的實例,這意味着您必須重新實現該類型的每個方法。 此外,有時您必須實現內置中未實現的其他方法。

例如,你可能認為子類化list ,因為list只實現了__iadd____add__你可以安全地重新實現這兩種方法,但你錯了! 您還必須實現__radd__ ,否則表達式如下:

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

將返回正常list而不是MyList

總而言之,對內置子類進行子類化的后果比您在開始時的想法要多得多,並且由於您沒有預料到的類型或行為的更改,它可能會引入一些不可預測的錯誤。 調試也變得更難,因為你不能簡單地在日志中打印對象的實例,表示是正確的! 你真的必須檢查周圍所有對象的類來捕捉這個微妙的錯誤。

在您的特定情況下,如果您計划僅在單個方法中轉換字典,那么您可以避免子類化dict大多數缺點,但在那時為什么不簡單地編寫函數並將dict與它進行比較? 除非您想將dict傳遞給執行比較的庫函數,否則這應該可以正常工作。

僅供參考,我認為在我的情況下,子類化並不是最好的方法。 我已經制定了一個我最有可能在這里使用的解決方案。

這不是公認的答案,因為它是基於我從這個線程學到的東西的協作方法。 只是想要一個其他人可以從中受益的“解決方案”。

要覆蓋比較運算符,您需要定義使用不同運算符的派生類。 所以你不能按照你的建議去做。 你可以做的是派生一個“模糊浮點”類(如@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

但是,我應該補充一點,你冒着不確定性的風險:你的詞典會比較相等,但包含的數字略有不同,因此,后續計算可能會給你相等的結果,具體取決於你使用的字典。 在我看來,一種更安全(和更健全)的方法是在你將它們插入字典時對你的浮點數進行舍入,這樣它們就可以比較嚴格相等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM