7

辞書のペアを比較し、「ファジー」浮動小数点比較を使用numpy.allclose()するか、それを使用することをお勧めします。ただし、dicts にデフォルト==または!=Python を使用しても、これは行われません。

浮動小数点比較操作を変更する方法があるかどうか疑問に思っていました (おそらく、安全なクリーンアップのためにコンテキスト マネージャーを使用します)。

ここで例が役立つと思います。あらゆる種類の値を含む、深くネストされた dict があります。これらの値の一部は浮動小数点値です。浮動小数点値などを「比較」するための落とし穴がたくさんあることを私は知っています.

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

これらの 2 つの dict を比較して、唯一の違いが特定の範囲内の浮動小数点数である場合!=に返すようにしたいと思います。Trueたとえば、値が近い場合は異なる値をカウントしないでください(必要な精度はまだわかりません)。

私は自分で辞書を再帰的に調べて、手動でnumpy.allclose()浮動小数点値を使用し、他のすべての型の通常の等価性テストにフォールバックすることができると思います。ただし、これは少し注意が必要で、エラーが発生しやすくなります。これは受け入れられる解決策だと思いますし、そのようなものを見たいと思っています。うまくいけば、もっとエレガントなものがあります。

私の頭の中のエレガントなソリューションは、次のようになります。ただし、次のようなことが可能かどうかはわかりません。

with hacked_float_compare:
    result = d1 != d2

したがって、このコンテキスト マネージャー内では、浮動小数点比較を置き換えます (標準float()値については、独自の比較またはnumpy.allclose().

繰り返しますが、これが可能かどうかはわかりません。モンキー パッチは .NETfloat()で書かれているため、実際には実行できないからですC。また、辞書内のすべての浮動小数点値を、__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機能 (例: copysetdefault、など)getが提供されます。update


組み込みをサブクラス化してはならない理由について。

この解決策は簡単で正しいように見えますが、一般的にはそうではありません。まず第一に、組み込み型はサブクラス化できますが、それはサブクラスとして使用するために書かれたという意味ではありません。

また、おそらく組み込みメソッドを使用したいと思うでしょうが、これらのメソッドはクラスのインスタンスではなく組み込み型のインスタンスを返します。つまり、その型のすべてのメソッドを再実装する必要があります。また、組み込みで実装されていない他のメソッドを実装する必要がある場合もあります。

たとえば、サブクラス化すると、実装のみであり、これら 2 つのメソッドを安全に再実装listできると考えるかもしれませんが、それは間違いです! また、次のような式を実装する必要があります。list__iadd____add____radd__

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

listではなく通常の値を返しMyListます。

要約すると、ビルトインをサブクラス化すると、最初に考えたよりもはるかに多くの結果が生じ、予期しない型や動作の変更により、予期しないバグが発生する可能性があります。ログ内のオブジェクトのインスタンスを単に出力することはできないため、デバッグも難しくなります。表現は正しいでしょう! この微妙なバグを見つけるには、周囲のすべてのオブジェクトのクラスを確認する必要があります。

特定の状況で、単一のメソッド内でのみ辞書を変換する場合は、サブクラス化のほとんどの欠点を回避できますdictが、その時点で単純に関数を記述してdicts を比較してみませんか? dict比較を行うライブラリ関数に s を渡したい場合を除いて、これはうまくいくはずです。

于 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、コードに書き込むことはできます。float を含む可能性のあるすべての (ネストされた) 辞書fuzzydictの代わりに必ず使用してください。dict

ただし、不確定性のリスクがあることを付け加えておく必要があります。辞書は等しいと比較されますが、わずかに異なる数値が含まれているため、使用する辞書によっては、その後の計算で等しくない結果が得られる可能性があります。私の意見では、より安全な(そしてより健全な)アプローチは、フロートを辞書に挿入するときにフロートを丸めることであり、厳密に等しくなります。

于 2012-12-06T18:05:24.937 に答える