[英]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
功能(例如copy
, setdefault
, get
, update
等)。
關於為什么你不應該為內置子類化。
這個解決方案似乎簡單而正確,但通常不是。 首先,即使您可以創建內置類型的子類,但這並不意味着它們被編寫為用作子類,因此您可能會發現要使某些內容有效,您必須編寫比您想象的更多的代碼。
此外,您可能希望使用內置方法,但這些方法將返回內置類型的實例而不是類的實例,這意味着您必須重新實現該類型的每個方法。 此外,有時您必須實現內置中未實現的其他方法。
例如,你可能認為子類化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.