4

オブジェクトのセットがあり、そのセットから特定のオブジェクトを取得することに興味があります。いくつかの調査の後、私はここで提供されるソリューションを使用することにしました:http: //code.activestate.com/recipes/499299/

問題は、それが機能していないように見えることです。

私はそのように定義された2つのクラスを持っています:

class Foo(object):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c
    def __key(self):
        return (self.a, self.b, self.c)
    def __eq__(self, other):
        return self.__key() == other.__key()
    def __hash__(self):
        return hash(self.__key())

class Bar(Foo):
    def __init__(self, a, b, c, d, e):
        self.a = a
        self.b = b
        self.c = c
        self.d = d
        self.e = e

注:これら2つのクラスの同等性は、属性a、b、cでのみ定義する必要があります。

http://code.activestate.com/recipes/499299/のラッパーも_CaptureEq、独自のメソッドを定義しています。問題は、このメソッドが呼び出されないことです(私は思います)。検討、__eq__

bar_1 = Bar(1,2,3,4,5)
bar_2 = Bar(1,2,3,10,11)
summary = set((bar_1,))
assert(bar_1 == bar_2)
bar_equiv = get_equivalent(summary, bar_2)

bar_equiv.d4に等しく、同様bar_equiv .eに5に等しくなければなりませんが、そうではありません。前述したように、ステートメントの実行 __CaptureEq __eq__時にメソッドが呼び出されないようです。bar_2 in summary

__CaptureEq __eq__メソッドが呼び出されない理由はありますか?うまくいけば、これは質問があまりにも曖昧ではありません。

4

3 に答える 3

7

ブランドンの答えは有益ですが、正しくありません。実際には2つの問題があります。1つは古いスタイルのクラスとして記述されているレシピに依存し_CaptureEqているため(ハッシュベースのコンテナーを使用してPython 3で試してみると正しく機能しません)、もう1つは独自のFoo.__eq__定義で主張しています。 「わからない、等しいかどうか他のオブジェクトに尋ねる」と言うべきときに、2つのオブジェクトが等しくないことは間違いありません。

__hash__レシピの問題を修正するのは簡単です。比較ラッパークラスで定義するだけです。

class _CaptureEq:
    'Object wrapper that remembers "other" for successful equality tests.'
    def __init__(self, obj):
        self.obj = obj
        self.match = obj
    # If running on Python 3, this will be a new-style class, and
    # new-style classes must delegate hash explicitly in order to populate
    # the underlying special method slot correctly.
    # On Python 2, it will be an old-style class, so the explicit delegation
    # isn't needed (__getattr__ will cover it), but it also won't do any harm.
    def __hash__(self):
        return hash(self.obj)
    def __eq__(self, other):
        result = (self.obj == other)
        if result:
            self.match = other
        return result
    def __getattr__(self, name):  # support anything else needed by __contains__
        return getattr(self.obj, name)

独自の__eq__定義の問題も簡単に修正できます。NotImplemented適切な場合に戻って、未知のオブジェクトとの比較に決定的な答えを提供すると主張しないようにします。

class Foo(object):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c
    def __key(self):
        return (self.a, self.b, self.c)
    def __eq__(self, other):
        if not isinstance(other, Foo):
            # Don't recognise "other", so let *it* decide if we're equal
            return NotImplemented
        return self.__key() == other.__key()
    def __hash__(self):
        return hash(self.__key())

get_equivalentこれらの2つの修正により、レイモンドのレシピが正しく機能することがわかります。

>>> from capture_eq import *
>>> bar_1 = Bar(1,2,3,4,5)
>>> bar_2 = Bar(1,2,3,10,11)
>>> summary = set((bar_1,))
>>> assert(bar_1 == bar_2)
>>> bar_equiv = get_equivalent(summary, bar_2)
>>> bar_equiv.d
4
>>> bar_equiv.e
5

更新:明示的な__hash__オーバーライドは、Python3のケースを正しく処理するためにのみ必要であることを明確にしました。

于 2012-06-25T04:40:38.790 に答える
4

問題は、setこのパターンの2つのオブジェクトを「間違った方法」で比較して、への呼び出しをインターセプトすること__eq__()です。2006年のレシピは、コンテナに対して作成されたものであり、存在するかどうかxを尋ねられたときに、コンテナにすでに存在する候補y値を次のように調べました。

x == y

比較。この場合、__eq__()onxは検索中に特別なアクションを実行できます。しかし、setオブジェクトは逆の方法で比較を行っています。

y == x

セット内の各yに対して。したがって、データ型が。の場合、このパターンはこの形式では使用できない可能性がありますsetFoo.__eq__()これは、次のように計測することで確認できます。

def __eq__(self, other):
    print '__eq__: I am', self.d, self.e, 'and he is', other.d, other.e
    return self.__key() == other.__key()

次に、次のようなメッセージが表示されます。

__eq__: I am 4 5 and he is 10 11

同等性の比較が、すでにセットに含まれているオブジェクトに同等性の質問を投げかけていることを確認します。つまり、残念ながら、ヘッティンジャーのオブジェクトでラップされたオブジェクトではありません_CaptureEq

アップデート:

そして、私は前進する方法を提案するのを忘れました:あなたは辞書を使うことを考えましたか?ここでは、オブジェクト内のデータのサブセットであるキーのアイデアがあるため、キーのアイデアをオブジェクト自体のアイデアから分割すると、この種の複雑なオブジェクトの傍受を試みる必要性が軽減される場合があります。 。オブジェクトとディクショナリが与えられると、キーを計算してディクショナリを調べ、キーが存在する場合はディクショナリにすでに存在するオブジェクトを返し、それ以外の場合はキーに新しいオブジェクトを挿入する新しい関数を作成するだけです。

更新2:ええと、それを見てください—ニックの答えは、NotImplemented一方の方向でaを使用して、もう一方の方向で比較を実行するように強制します。set男にいくつかの+1を与えてください!

于 2012-06-25T01:55:36.973 に答える
-2

ここには2つの問題があります。1つ目は次のとおりです。

t = _CaptureEq(item)
if t in container:
    return t.match
return default

あなたが思うことをしません。特に、は定義されていないため、に含まtれることはありません。これは、デフォルトを提供するのではなく、これを指摘するため、Python3でより明白になります。のコードは、を提供することでこれを解決できると信じているようです-Pythonの特別なメソッドルックアップは、通常の属性ルックアップと同じ手順をすべて実行することが保証されていないため、解決しません-これは、(および他のさまざまな)必要があるのと同じ理由ですクラスで定義され、インスタンスにモンキーパッチを適用することはできません。したがって、これを回避する最も直接的な方法は、次のように定義することです。container_CaptureEq__hash____hash___CaptureEq__getattr____hash___CaptureEq.__hash__

def __hash__(self):
   return hash(self.obj)

ただし、2番目の問題があるため、これが機能することはset保証されていません。ルックアップが同等性をテストすることは保証されていません。setはハッシュテーブルに基づいており、ハッシュバケットに複数のアイテムがある場合にのみ同等性テストを実行します。異なる方法でハッシュするアイテムを同じバケットに強制することはできません(また、したくありません)。これは、すべての実装の詳細であるためですset。この問題を回避し、最初の問題を適切に回避する最も簡単な方法は、代わりにリストを使用することです。

summary = [bar_1]
assert(bar_1 == bar_2)
bar_equiv = get_equivalent(summary, bar_2)
assert(bar_equiv is bar_1)
于 2012-06-25T02:35:07.807 に答える