Pythonハッシュ可能辞書


92

演習として、そして主に私自身の娯楽のために、私はバックトラッキングpackratパーサーを実装しています。このためのインスピレーションは、アルゴルのような言語でハイジェニックマクロがどのように機能するかについて、もっとよく理解したいと思います(通常、構文構文のないlisp方言に見られるように)。このため、入力を通過するパスによって文法が異なる可能性があるため、キャッシュされた解析結果とともに現在のバージョンの文法も保存しない限り、キャッシュされた解析結果は無効です。(編集:キーと値のコレクションのこの使用の結果は、それらが不変でなければならないということですが、それらを変更できるようにするためにインターフェースを公開するつもりはないので、可変または不変のコレクションは問題ありません)

問題は、Python dictが他のdictのキーとして表示できないことです。タプルを使っても(とにかくやっているように)助けにはなりません。

>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> 

ずっと下のタプルである必要があると思います。現在、Python標準ライブラリは、ほぼ必要なものを提供しcollections.namedtuple、非常に異なる構文を持っていますが、キーとして使用できます。上記のセッションから続く:

>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}

OK。しかし、私は使用したいルールのキーの可能な組み合わせごとにクラスを作成する必要があります。これはそれほど悪くはありません。各解析ルールが使用するパラメーターを正確に認識しているため、クラスを同時に定義できますルールを解析する関数として。

編集:namedtuples のもう1つの問題は、それらが厳密に定位置にあることです。異なるように見える2つのタプルは、実際には同じである可能性があります。

>>> you = namedtuple("foo",["bar","baz"])
>>> me = namedtuple("foo",["bar","quux"])
>>> you(bar=1,baz=2) == me(bar=1,quux=2)
True
>>> bob = namedtuple("foo",["baz","bar"])
>>> you(bar=1,baz=2) == bob(bar=1,baz=2)
False

tl'dr:dictdictののキーとして使用できるを取得するにはどうすればよいですか?

答えに少し手を加えたので、これが私が使用しているより完全なソリューションです。これは、結果として得られるディクテーションを実際的な目的のために漠然と不変にするために少し余分な作業を行うことに注意してください。もちろん、電話でハッキングするのは非常に簡単dict.__setitem__(instance, key, value)ですが、私たちはここではすべて大人です。

class hashdict(dict):
    """
    hashable dict implementation, suitable for use as a key into
    other dicts.

        >>> h1 = hashdict({"apples": 1, "bananas":2})
        >>> h2 = hashdict({"bananas": 3, "mangoes": 5})
        >>> h1+h2
        hashdict(apples=1, bananas=3, mangoes=5)
        >>> d1 = {}
        >>> d1[h1] = "salad"
        >>> d1[h1]
        'salad'
        >>> d1[h2]
        Traceback (most recent call last):
        ...
        KeyError: hashdict(bananas=3, mangoes=5)

    based on answers from
       http://stackoverflow.com/questions/1151658/python-hashable-dicts

    """
    def __key(self):
        return tuple(sorted(self.items()))
    def __repr__(self):
        return "{0}({1})".format(self.__class__.__name__,
            ", ".join("{0}={1}".format(
                    str(i[0]),repr(i[1])) for i in self.__key()))

    def __hash__(self):
        return hash(self.__key())
    def __setitem__(self, key, value):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def __delitem__(self, key):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def clear(self):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def pop(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def popitem(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def setdefault(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def update(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    # update is not ok because it mutates the object
    # __add__ is ok because it creates a new object
    # while the new object is under construction, it's ok to mutate it
    def __add__(self, right):
        result = hashdict(self)
        dict.update(result, right)
        return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()

hashdictあなたはそれをハッシュ開始した後、なぜキャッシュしませ少なくとも、不変でなければならないkeyhashの属性として値をhashdictオブジェクト?を変更__key()__hash__()てテストし、はるかに高速であることを確認しました。SOではコメント内のフォーマットされたコードは許可されていないため、ここにリンクします。sam.aiki.info
Sam Watkins

回答:


68

これがハッシュ可能な辞書を作る簡単な方法です。明らかな理由で、別の辞書に埋め込んだ後にそれらを変更しないことを覚えておいてください。

class hashabledict(dict):
    def __hash__(self):
        return hash(tuple(sorted(self.items())))

7
これはeqハッシュの一貫性を大幅に保証するものではありませんが、以前の回答は__keyメソッドを使用して行います(実際にはどちらの方法でも機能しますが、不要な反復リストを作成すると速度が低下する可能性があります-s / itemsで修正可能) / iteritems /-言わないようにPython 2. *を想定しています;-)。
Alex Martelli、

5
ソート付きのタプルではなく、frozensetを使用する方がおそらく良いでしょう。これはより高速になるだけでなく、辞書のキーが同等であるとは想定できません。
asmeurer 2012

1
エントリの数がO(n*log(n))どこにあるかハッシュ関数を回避する方法があるはずです。Pythonのハッシュ関数が線形時間で実行されるかどうか誰かが知っていますか?ndictfrozenset
トムKarzes

2
@HelloGoodbye辞書はまた、次のように作成することができるdict(key1=value1, key2=value2,...)か、これdict([(key1, value1), (key2, value2),...)])。同じことがこれにも当てはまります。あなたが投稿した作品はリテラル
smido

2
@smido:ありがとう。また、リテラル、つまりをキャストできることもわかりましたhashabledict({key_a: val_a, key_b: val_b, ...})
HelloGoodbye 2018年

62

ハッシュは不変である必要があります-これを強制するのではなく、最初にキーとして使用した後、dictを変更しないようにTRUSTINGする場合、次のアプローチが機能します。

class hashabledict(dict):
  def __key(self):
    return tuple((k,self[k]) for k in sorted(self))
  def __hash__(self):
    return hash(self.__key())
  def __eq__(self, other):
    return self.__key() == other.__key()

辞書を変更する必要があり、それらをキーとして使用したい場合は、複雑さが100倍に爆発します-不可能ではありませんが、非常に具体的な兆候が出るまで待ってから、信じられないほどの泥沼に入ります! -)


ディクテーションが準備されたら、私はディクテーションを変更したくありません。これにより、残りのpackradアルゴリズムがバラバラになります。
SingleNegationElimination 2009

そして、サブクラスは、私が働く提案-それは(「位置」の問題を迂回方法に注意してくださいする前にあなたが;-)それを指摘して、あなたの質問に編集したsorted__keyでの;-)。
Alex Martelli、

namedtupleの位置に依存する動作は、私を驚かせました。私はそれはまだ問題を解決する簡単な方法かもしれないが、それはほとんどすべて私の希望を破線考えて、それで遊んで、(およびロールバックが必要になります:()されていた
SingleNegationElimination

dictがあり、それをhashabledictにキャストしたいとします。どうすればいいですか?
jononomo 2014年

:アイデアと明確化のためにこれらの質問を参照@JonCrowell stackoverflow.com/questions/3464061/...stackoverflow.com/questions/9112300/...stackoverflow.com/questions/18020074/...
最大

32

辞書を目的に合わせて使用​​できるようにするために必要なことは、__ hash__メソッドを追加することだけです。

class Hashabledict(dict):
    def __hash__(self):
        return hash(frozenset(self))

なお、frozenset変換はすべての辞書で機能します(つまり、キーをソート可能にする必要はありません)。同様に、辞書の値に制限はありません。

キーは同じで値が異なる辞書が多数ある場合は、ハッシュで値を考慮する必要があります。これを行う最速の方法は次のとおりです。

class Hashabledict(dict):
    def __hash__(self):
        return hash((frozenset(self), frozenset(self.itervalues())))

これはfrozenset(self.iteritems())2つの理由よりも速いです。最初に、frozenset(self)ステップは辞書に格納されているハッシュ値を再利用し、hash(key)ます。次に、itervaluesを使用すると、値に直接アクセスし、ルックアップを実行するたびに、アイテムごとに使用する多くのメモリアロケーターコールを回避して、メモリ内に新しい多数のキー/値タプルを形成します。


@RaymondHettinger私が間違っている場合は修正してください。ただし、dictそれ自体はそのキーのハッシュ値をキャッシュしないと思いました。ただし、個々のクラス(などstr)は、ハッシュをキャッシュすることを選択している場合があります。少なくともdict、カスタムクラスインスタンスをキーとして使用してを作成した場合、それらの__hash__メソッドはすべてのアクセス操作で呼び出されました(python 3.4)。私が正しいかどうかにかかわらずhash(frozenset(self))、事前に計算されたハッシュ値がキー自体の内部にキャッシュされていない限り(その場合はhash(frozenset(self.items())それらも再利用します)、事前に計算されたハッシュ値をどのように再利用できるかわかりません。
最大

(キー/値)タプルの作成に関する2番目のポイントについて、.items()メソッドはタプルのリストではなくビューを返すこと、そしてそのビューの作成には基礎となるキーと値のコピーが含まれないことを考えました。(Python 3.4を再度使用します。)とはいえ、ほとんどの入力に異なるキーがある場合、キーだけをハッシュする利点はわかります。(1)値をハッシュするのは非常にコストがかかり、(2)値がハッシュ可能であることを要求することは非常に制限されるためです。
最大

6
これは、2つの異なる辞書に対して同じハッシュを作成する可能性もあります。考えてみましょう{'one': 1, 'two': 2}{'one': 2, 'two': 1}
AgDude

Mike Grahamのコメントの中で、他の理由__missing__ディクテーション導出すること定義することは悪い考えであると述べていますどう思いますか?
Piotr Dobrogost 2016年

1
dictからのサブクラス化は、Python 2.2以降、明確に定義されています。Python標準ライブラリの例については、collections.OrderedDictとcollections.Counterをご覧ください。他のコメントは、MutableMappingのサブクラスのみが適切に定義されているという根拠のない信念に基づいています。
レイモンドヘッティンガー2016年

23

与えられた答えは大丈夫ですが、ハッシュを生成するfrozenset(...)代わりにtuple(sorted(...))を使用することで改善できます:

>>> import timeit
>>> timeit.timeit('hash(tuple(sorted(d.iteritems())))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
4.7758948802947998
>>> timeit.timeit('hash(frozenset(d.iteritems()))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
1.8153600692749023

パフォーマンスの利点はディクショナリの内容に依存しますが、ほとんどの場合、私がテストしたところ、ハッシュfrozensetは少なくとも2倍高速です(主にソートする必要がないため)。


1
キーと値の両方を含める必要はありません。このソリューションは次のようにはるかに高速になります hash(frozenset(d))
レイモンドヘッティンガー2013

10
@RaymondHettinger:hash(frozenset(d))キーは似ているが値が異なる2つのdict で同じハッシュになります!
Oben Sonne 2013

4
それは問題ではありません。__eq__の役割は、異なる値の辞書を区別することです。__hash__の仕事は、単に検索スペースを減らすことです。
レイモンドヘッティンガー2013

5
これは、ハッシュとマッピングの理論的概念には当てはまりますが、ルックアップとしてディクショナリを持つキャッシュには実用的ではありません。類似したキーを持つディクショナリが異なる値がmem-cached関数に渡されることは珍しくありません。その場合、ハッシュの作成にキーのみが使用されると、キャッシュはマッピングではなくリストに変わります。
Oben Sonne 2013

3
同一のキーと個別の値を持つdictの特殊なケースでは、に基づくハッシュを保存するだけのほうがベターfrozenset(d.itervalues())です。dictが異なるキーを持っている場合frozenset(d)は、はるかに速く、キーのハッシュ可能性に制限はありません。最後に、dict .__ eq__メソッドは、すべてのキー/値のペアのタプルのハッシュを計算できるよりも、等しいキー/値のペアをはるかに迅速にチェックすることに注意してください。キー/値タプルを使用すると、すべてのキーの格納されたハッシュが破棄されるため、問題があります(そのfrozenset(d)ため、非常に高速です)。
レイモンドヘッティンガー2013

11

合理的にクリーンで簡単な実装は

import collections

class FrozenDict(collections.Mapping):
    """Don't forget the docstrings!!"""

    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)

    def __iter__(self):
        return iter(self._d)

    def __len__(self):
        return len(self._d)

    def __getitem__(self, key):
        return self._d[key]

    def __hash__(self):
        return hash(tuple(sorted(self._d.iteritems())))

なぜそれがそれほど合理的で、クリーンで簡単なのですか?つまり、__iter__やの必要性など、他の回答との違いを説明してください__len__
カールリヒター2014

1
@KarlRichter、私はそれが合理的であると言ったことはありません。;)
マイクグラハム

@KarlRichter、私は定義します __iter____len__ているのは、私が導出しているからcollections.Mappingです。使用方法collections.Mappingは、コレクションモジュールのドキュメントでかなり詳しく説明されています。他の人々は彼らが派生しているので必要性を感じませんdictdict他の理由で導出する__missing__が、定義することは悪い考えです。dict仕様は、そのような場合にdictがどのように機能するかを示していません。実際には、これは一般的にあまり役に立たない非仮想メソッドが大量に存在することになります。
マイクグラハム、

7

私はこのトピックに戻ってきます...別のバリエーションがあります。メソッドdictを追加するサブクラス化に不安があり__hash__ます。dictが変更可能であるという問題からの脱出は事実上なく、それらが変更されないことを信頼することは弱い考えのように思えます。そこで、代わりに、それ自体は不変である組み込み型に基づいてマッピングを構築する方法を検討しました。がtuple当然の選択で、その内の値にアクセスすると、ソートや二分することを意味。問題ではありませんが、その上に構築されたタイプのパワーの多くを活用しているようには見えません。

キーをジャムすると、値のペアが frozenset?それには何が必要ですか?それはどのように機能しますか?

第1部では、「アイテム」をフローズンセットが主にキーで処理するようにエンコードする方法が必要です。そのための小さなサブクラスを作成します。

import collections
class pair(collections.namedtuple('pair_base', 'key value')):
    def __hash__(self):
        return hash((self.key, None))
    def __eq__(self, other):
        if type(self) != type(other):
            return NotImplemented
        return self.key == other.key
    def __repr__(self):
        return repr((self.key, self.value))

それだけで、不変のマッピングの距離が大きく広がります。

>>> frozenset(pair(k, v) for k, v in enumerate('abcd'))
frozenset([(0, 'a'), (2, 'c'), (1, 'b'), (3, 'd')])
>>> pairs = frozenset(pair(k, v) for k, v in enumerate('abcd'))
>>> pair(2, None) in pairs
True
>>> pair(5, None) in pairs
False
>>> goal = frozenset((pair(2, None),))
>>> pairs & goal
frozenset([(2, None)])

ああ!残念ながら、セット演算子を使用すると、要素は同じですが同じオブジェクトではありません。どれが戻り値で終わるかはundefinedです。さらにいくつかの旋回を行う必要があります。

>>> pairs - (pairs - goal)
frozenset([(2, 'c')])
>>> iter(pairs - (pairs - goal)).next().value
'c'

ただし、この方法で値を検索するのは面倒であり、さらに悪いことに、多くの中間セットが作成されます。それはしません!それを回避するために「偽の」キーと値のペアを作成します。

class Thief(object):
    def __init__(self, key):
        self.key = key
    def __hash__(self):
        return hash(pair(self.key, None))
    def __eq__(self, other):
        self.value = other.value
        return pair(self.key, None) == other

これにより、問題が少なくなります。

>>> thief = Thief(2)
>>> thief in pairs
True
>>> thief.value
'c'

それがすべての深い魔法です。残りはそれを持っている何かにそれをすべて包んでいます dictのようなインターフェースますfrozenset非常に異なるインターフェースを持つからサブクラス化しているので、かなり多くのメソッドがあります。から少し助けを得ますが、ほとんどcollections.Mappingの作業は、frozensetdictsのように機能するバージョンのメソッドをオーバーライドしています。

class FrozenDict(frozenset, collections.Mapping):
    def __new__(cls, seq=()):
        return frozenset.__new__(cls, (pair(k, v) for k, v in seq))
    def __getitem__(self, key):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        raise KeyError(key)
    def __eq__(self, other):
        if not isinstance(other, FrozenDict):
            return dict(self.iteritems()) == other
        if len(self) != len(other):
            return False
        for key, value in self.iteritems():
            try:
                if value != other[key]:
                    return False
            except KeyError:
                return False
        return True
    def __hash__(self):
        return hash(frozenset(self.iteritems()))
    def get(self, key, default=None):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        return default
    def __iter__(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def iteritems(self):
        for item in frozenset.__iter__(self):
            yield (item.key, item.value)
    def iterkeys(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def itervalues(self):
        for item in frozenset.__iter__(self):
            yield item.value
    def __contains__(self, key):
        return frozenset.__contains__(self, pair(key, None))
    has_key = __contains__
    def __repr__(self):
        return type(self).__name__ + (', '.join(repr(item) for item in self.iteritems())).join('()')
    @classmethod
    def fromkeys(cls, keys, value=None):
        return cls((key, value) for key in keys)

これは、最終的には、私自身の質問に答えます。

>>> myDict = {}
>>> myDict[FrozenDict(enumerate('ab'))] = 5
>>> FrozenDict(enumerate('ab')) in myDict
True
>>> FrozenDict(enumerate('bc')) in myDict
False
>>> FrozenDict(enumerate('ab', 3)) in myDict
False
>>> myDict[FrozenDict(enumerate('ab'))]
5

5

@Unknownによる承認された回答と@AlexMartelliによる回答は完全に正常に機能しますが、次の制約がある場合のみです。

  1. 辞書の値はハッシュ可能でなければなりません。例えば、hash(hashabledict({'a':[1,2]}))が発生しTypeErrorます。
  2. キーは比較演算をサポートする必要があります。例えば、hash(hashabledict({'a':'a', 1:1}))上げるTypeErrorます。
  3. キーの比較演算子は、完全な順序付けを強制します。たとえば、ディクショナリの2つのキーがfrozenset((1,2,3))andのfrozenset((4,5,6))場合、両方向で等しくありません。したがって、そのようなキーを使用してディクショナリのアイテムをソートすると、任意の順序になる可能性があり、したがって、等しいオブジェクトは同じハッシュ値を持つ必要があるという規則に違反します。

@ObenSonneによるはるかに速い答えは、制約2と3を解除しますが、制約1によって拘束されます(値はハッシュ可能でなければなりません)。

@RaymondHettingerによる高速でありながら答え.values()は、ハッシュ計算に含まれていないため、3つの制約すべてを解除します。ただし、次の場合にのみパフォーマンスが向上します。

  1. ハッシュする必要がある(等しくない)辞書のほとんどは同一ではありません.keys()

この条件が満たされない場合でも、ハッシュ関数は有効ですが、衝突が多すぎる可能性があります。たとえば、すべての辞書がWebサイトテンプレートから生成される極端なケース(キーとしてのフィールド名、値としてのユーザー入力)では、キーは常に同じであり、ハッシュ関数はすべての入力に対して同じ値を返します。その結果、そのようなハッシュ関数に依存するハッシュテーブルは、アイテムを取得するときに(リストのO(N)代わりに)リストと同じくらい遅くなりますO(1))ます。

上記の4つの制約すべてに違反しても、次の解決策は適切に機能すると思います。入れ子になった可変コンテナがあっても、辞書だけでなくすべてのコンテナをハッシュできるという追加の利点があります。

これまでは軽くテストしただけなので、これに対するフィードバックはありがたいです。

# python 3.4
import collections
import operator
import sys
import itertools
import reprlib

# a wrapper to make an object hashable, while preserving equality
class AutoHash:
    # for each known container type, we can optionally provide a tuple
    # specifying: type, transform, aggregator
    # even immutable types need to be included, since their items
    # may make them unhashable

    # transformation may be used to enforce the desired iteration
    # the result of a transformation must be an iterable
    # default: no change; for dictionaries, we use .items() to see values

    # usually transformation choice only affects efficiency, not correctness

    # aggregator is the function that combines all items into one object
    # default: frozenset; for ordered containers, we can use tuple

    # aggregator choice affects both efficiency and correctness
    # e.g., using a tuple aggregator for a set is incorrect,
    # since identical sets may end up with different hash values
    # frozenset is safe since at worst it just causes more collisions
    # unfortunately, no collections.ABC class is available that helps
    # distinguish ordered from unordered containers
    # so we need to just list them out manually as needed

    type_info = collections.namedtuple(
        'type_info',
        'type transformation aggregator')

    ident = lambda x: x
    # order matters; first match is used to handle a datatype
    known_types = (
        # dict also handles defaultdict
        type_info(dict, lambda d: d.items(), frozenset), 
        # no need to include set and frozenset, since they are fine with defaults
        type_info(collections.OrderedDict, ident, tuple),
        type_info(list, ident, tuple),
        type_info(tuple, ident, tuple),
        type_info(collections.deque, ident, tuple),
        type_info(collections.Iterable, ident, frozenset) # other iterables
    )

    # hash_func can be set to replace the built-in hash function
    # cache can be turned on; if it is, cycles will be detected,
    # otherwise cycles in a data structure will cause failure
    def __init__(self, data, hash_func=hash, cache=False, verbose=False):
        self._data=data
        self.hash_func=hash_func
        self.verbose=verbose
        self.cache=cache
        # cache objects' hashes for performance and to deal with cycles
        if self.cache:
            self.seen={}

    def hash_ex(self, o):
        # note: isinstance(o, Hashable) won't check inner types
        try:
            if self.verbose:
                print(type(o),
                    reprlib.repr(o),
                    self.hash_func(o),
                    file=sys.stderr)
            return self.hash_func(o)
        except TypeError:
            pass

        # we let built-in hash decide if the hash value is worth caching
        # so we don't cache the built-in hash results
        if self.cache and id(o) in self.seen:
            return self.seen[id(o)][0] # found in cache

        # check if o can be handled by decomposing it into components
        for typ, transformation, aggregator in AutoHash.known_types:
            if isinstance(o, typ):
                # another option is:
                # result = reduce(operator.xor, map(_hash_ex, handler(o)))
                # but collisions are more likely with xor than with frozenset
                # e.g. hash_ex([1,2,3,4])==0 with xor

                try:
                    # try to frozenset the actual components, it's faster
                    h = self.hash_func(aggregator(transformation(o)))
                except TypeError:
                    # components not hashable with built-in;
                    # apply our extended hash function to them
                    h = self.hash_func(aggregator(map(self.hash_ex, transformation(o))))
                if self.cache:
                    # storing the object too, otherwise memory location will be reused
                    self.seen[id(o)] = (h, o)
                if self.verbose:
                    print(type(o), reprlib.repr(o), h, file=sys.stderr)
                return h

        raise TypeError('Object {} of type {} not hashable'.format(repr(o), type(o)))

    def __hash__(self):
        return self.hash_ex(self._data)

    def __eq__(self, other):
        # short circuit to save time
        if self is other:
            return True

        # 1) type(self) a proper subclass of type(other) => self.__eq__ will be called first
        # 2) any other situation => lhs.__eq__ will be called first

        # case 1. one side is a subclass of the other, and AutoHash.__eq__ is not overridden in either
        # => the subclass instance's __eq__ is called first, and we should compare self._data and other._data
        # case 2. neither side is a subclass of the other; self is lhs
        # => we can't compare to another type; we should let the other side decide what to do, return NotImplemented
        # case 3. neither side is a subclass of the other; self is rhs
        # => we can't compare to another type, and the other side already tried and failed;
        # we should return False, but NotImplemented will have the same effect
        # any other case: we won't reach the __eq__ code in this class, no need to worry about it

        if isinstance(self, type(other)): # identifies case 1
            return self._data == other._data
        else: # identifies cases 2 and 3
            return NotImplemented

d1 = {'a':[1,2], 2:{3:4}}
print(hash(AutoHash(d1, cache=True, verbose=True)))

d = AutoHash(dict(a=1, b=2, c=3, d=[4,5,6,7], e='a string of chars'),cache=True, verbose=True)
print(hash(d))

2

また、これら2つのメソッドを追加して、v2ピックリングプロトコルをhashdictインスタンスで機能させることもできます。そうでない場合、cPickleはhashdict .____ setitem____を使用しようとし、TypeErrorが発生します。興味深いことに、他の2つのバージョンのプロトコルでは、コードは正常に機能します。

def __setstate__(self, objstate):
    for k,v in objstate.items():
        dict.__setitem__(self,k,v)
def __reduce__(self):
    return (hashdict, (), dict(self),)

-2

辞書に数値を入れず、辞書を含む変数を失うことがない場合は、次のようにできます。

cache[id(rule)] = "whatever"

id()はすべての辞書で一意であるため

編集:

ああ、申し訳ありませんが、その場合は他の人が言ったほうがいいでしょう。次のように、辞書を文字列としてシリアル化することもできます。

cache[ 'foo:bar' ] = 'baz'

キーから辞書を復元する必要がある場合は、次のような醜いことをする必要があります

cache[ 'foo:bar' ] = ( {'foo':'bar'}, 'baz' )

これの利点は、それほど多くのコードを記述する必要がないことだと思います。


うーん、いいえ。これは私が探しているものではありません:cache[id({'foo':'bar'})] = 'baz'; id({'foo':'bar'}) not in cache、動的にキーを作成できることは、そもそも辞書をキーとして使用したいときに重要です。
SingleNegationElimination 2012

1
辞書をシリアル化することは問題ないかもしれませんが、それらをシリアル化する方法についての推奨はありますか?それが私が探しているものです。
SingleNegationElimination 2012年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.