キャッシュの目的で、dictに存在するGET引数からキャッシュキーを生成する必要があります。
現在私はsha1(repr(sorted(my_dict.items())))
(hashlibを内部sha1()
で使用する便利な方法です)を使用していますが、もっと良い方法があるかどうか知りたいです。
{'a': 1, 'b':2}
は意味的には)と同じ{'b':2, 'a':1}
です。YMMVは複雑すぎてまだ使用していませんが、フィードバックは歓迎します。
キャッシュの目的で、dictに存在するGET引数からキャッシュキーを生成する必要があります。
現在私はsha1(repr(sorted(my_dict.items())))
(hashlibを内部sha1()
で使用する便利な方法です)を使用していますが、もっと良い方法があるかどうか知りたいです。
{'a': 1, 'b':2}
は意味的には)と同じ{'b':2, 'a':1}
です。YMMVは複雑すぎてまだ使用していませんが、フィードバックは歓迎します。
回答:
辞書がネストされていない場合は、dictのアイテムでフローズンセットを作成し、次を使用できますhash()
。
hash(frozenset(my_dict.items()))
これは、JSON文字列やディクショナリの表現を生成するよりも、計算負荷がはるかに少なくなります。
更新:以下のコメントを参照してください。なぜこのアプローチでは安定した結果が得られないのでしょうか。
hash()
関数が安定した出力を生成しないのは興味深いかもしれません。これは、同じ入力が与えられた場合、同じPythonインタープリターの異なるインスタンスで異なる結果を返すことを意味します。私には、インタープリターが開始されるたびに、ある種のシード値が生成されるように見えます。
を使用しsorted(d.items())
ても、安定した収益を得るためには不十分です。の値の一部d
も辞書である可能性があり、それらのキーは任意の順序で出力されます。すべてのキーが文字列である限り、以下を使用することをお勧めします。
json.dumps(d, sort_keys=True)
そうは言っても、ハッシュが異なるマシンまたはPythonバージョン間で安定している必要がある場合、これが完全なものであるかどうかはわかりません。separators
とensure_ascii
引数を追加して、デフォルトの変更から保護することができます。コメントいただければ幸いです。
ensure_ascii
引数は、この完全に仮想的な問題から保護します。
make_hash
。gist.github.com/charlax/b8731de51d2ea86c6eb9
default=str
するdumps
コマンド。うまく機能しているようです。
編集:すべてのキーが文字列の場合、この回答を読み続ける前に、Jack O'Connorの非常にシンプルな(そして高速な)ソリューションを参照してください(ネストされた辞書のハッシュにも機能します)。
回答は受理されましたが、質問のタイトルは「Python辞書のハッシュ化」であり、タイトルに関しては回答が不完全です。(質問の本文に関しては、答えは完全です。)
ネストされた辞書
辞書をハッシュする方法をStack Overflowで検索すると、この適切にタイトルが付けられた質問に出くわし、ネストされた複数の辞書をハッシュしようとすると、満足できなくなります。この場合、上記の答えは機能せず、ハッシュを取得するために何らかの再帰的なメカニズムを実装する必要があります。
そのようなメカニズムの1つを次に示します。
import copy
def make_hash(o):
"""
Makes a hash from a dictionary, list, tuple or set to any level, that contains
only other hashable types (including any lists, tuples, sets, and
dictionaries).
"""
if isinstance(o, (set, tuple, list)):
return tuple([make_hash(e) for e in o])
elif not isinstance(o, dict):
return hash(o)
new_o = copy.deepcopy(o)
for k, v in new_o.items():
new_o[k] = make_hash(v)
return hash(tuple(frozenset(sorted(new_o.items()))))
おまけ:オブジェクトとクラスのハッシュ
このhash()
関数は、クラスまたはインスタンスをハッシュするときに最適です。ただし、オブジェクトに関しては、ハッシュに関して私が見つけた1つの問題があります。
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
fooを変更した後でも、ハッシュは同じです。これは、fooのIDが変更されていないため、ハッシュは同じだからです。fooの現在の定義に応じて異なる方法でハッシュしたい場合、解決策は、実際に変更されているものはすべてハッシュ化することです。この場合、__dict__
属性は次のとおりです。
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
悲しいかな、あなたがクラス自体で同じことをやろうとしたとき:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
クラス__dict__
プロパティは通常の辞書ではありません:
print (type(Foo.__dict__)) # type <'dict_proxy'>
これは、クラスを適切に処理する、以前と同様のメカニズムです。
import copy
DictProxyType = type(object.__dict__)
def make_hash(o):
"""
Makes a hash from a dictionary, list, tuple or set to any level, that
contains only other hashable types (including any lists, tuples, sets, and
dictionaries). In the case where other kinds of objects (like classes) need
to be hashed, pass in a collection of object attributes that are pertinent.
For example, a class can be hashed in this fashion:
make_hash([cls.__dict__, cls.__name__])
A function can be hashed like so:
make_hash([fn.__dict__, fn.__code__])
"""
if type(o) == DictProxyType:
o2 = {}
for k, v in o.items():
if not k.startswith("__"):
o2[k] = v
o = o2
if isinstance(o, (set, tuple, list)):
return tuple([make_hash(e) for e in o])
elif not isinstance(o, dict):
return hash(o)
new_o = copy.deepcopy(o)
for k, v in new_o.items():
new_o[k] = make_hash(v)
return hash(tuple(frozenset(sorted(new_o.items()))))
これを使用して、必要な要素のハッシュタプルを返すことができます。
# -7666086133114527897
print (make_hash(func.__code__))
# (-7666086133114527897, 3527539)
print (make_hash([func.__code__, func.__dict__]))
# (-7666086133114527897, 3527539, -509551383349783210)
print (make_hash([func.__code__, func.__dict__, func.__name__]))
注:上記のコードはすべてPython 3.xを想定しています。以前のバージョンではテストしていませんでしたmake_hash()
が、たとえば2.7.2で動作すると思います。例を機能させる限り、私はそれを知っています
func.__code__
に置き換える必要があります
func.func_code
hash
リストとタプルの周りへの呼び出しも追加しました。それ以外の場合は、たまたま私の辞書の値である整数のリストを受け取り、ハッシュのリストを返しますが、これは私が望むものではありません。
これはより明確な解決策です。
def freeze(o):
if isinstance(o,dict):
return frozenset({ k:freeze(v) for k,v in o.items()}.items())
if isinstance(o,list):
return tuple([freeze(v) for v in o])
return o
def make_hash(o):
"""
makes a hash out of anything that contains only list,dict and hashable types including string and numeric types
"""
return hash(freeze(o))
if isinstance(o,list):
するとif isinstance(obj, (set, tuple, list)):
、この関数はどのオブジェクトでも機能します。
以下のコードは、Pythonの再起動間で一貫したハッシュを提供しないため、Pythonのhash()関数の使用を避けています(Python 3.3のハッシュ関数はセッション間で異なる結果を返すを参照)。make_hashable()
オブジェクトをネストされたタプルにmake_hash_sha256()
変換しrepr()
、さらにbase64でエンコードされたSHA256ハッシュに変換します。
import hashlib
import base64
def make_hash_sha256(o):
hasher = hashlib.sha256()
hasher.update(repr(make_hashable(o)).encode())
return base64.b64encode(hasher.digest()).decode()
def make_hashable(o):
if isinstance(o, (tuple, list)):
return tuple((make_hashable(e) for e in o))
if isinstance(o, dict):
return tuple(sorted((k,make_hashable(v)) for k,v in o.items()))
if isinstance(o, (set, frozenset)):
return tuple(sorted(make_hashable(e) for e in o))
return o
o = dict(x=1,b=2,c=[3,4,5],d={6,7})
print(make_hashable(o))
# (('b', 2), ('c', (3, 4, 5)), ('d', (6, 7)), ('x', 1))
print(make_hash_sha256(o))
# fyt/gK6D24H9Ugexw+g3lbqnKZ0JAcgtNW+rXIDeU2Y=
make_hash_sha256(((0,1),(2,3)))==make_hash_sha256({0:1,2:3})==make_hash_sha256({2:3,0:1})!=make_hash_sha256(((2,3),(0,1)))
。これは私が探している解決策ではありませんが、良い中間体です。type(o).__name__
各タプルの先頭に追加して、差別化を強制することを考えています。
tuple(sorted((make_hashable(e) for e in o)))
2013年の返信から更新...
上記の答えはどれも私には信頼できるものではありません。その理由は、items()の使用です。私の知る限り、これはマシン依存の順序で出てきます。
代わりにこれはどうですか?
import hashlib
def dict_hash(the_dict, *ignore):
if ignore: # Sometimes you don't care about some items
interesting = the_dict.copy()
for item in ignore:
if item in interesting:
interesting.pop(item)
the_dict = interesting
result = hashlib.sha1(
'%s' % sorted(the_dict.items())
).hexdigest()
return result
dict.items
予測どおりに並べられたリストが返されないことが重要だと思いますか?frozenset
それを処理します
hash
は、frozensetの内容がどのように出力されるかなどを気にしないことを理解する必要があります。いくつかのマシンとpythonバージョンでテストしてみてください。
キーの順序を維持するために、代わりに、hash(str(dictionary))
またはhash(json.dumps(dictionary))
私は迅速で汚いソリューションを好みます:
from pprint import pformat
h = hash(pformat(dictionary))
DateTime
JSONシリアライズ可能ではないなどのタイプでも機能します。
サードパーティのfrozendict
モジュールを使用して、dictをフリーズしてハッシュ可能にすることができます。
from frozendict import frozendict
my_dict = frozendict(my_dict)
ネストされたオブジェクトを処理するには、次のようにします。
import collections.abc
def make_hashable(x):
if isinstance(x, collections.abc.Hashable):
return x
elif isinstance(x, collections.abc.Sequence):
return tuple(make_hashable(xi) for xi in x)
elif isinstance(x, collections.abc.Set):
return frozenset(make_hashable(xi) for xi in x)
elif isinstance(x, collections.abc.Mapping):
return frozendict({k: make_hashable(v) for k, v in x.items()})
else:
raise TypeError("Don't know how to make {} objects hashable".format(type(x).__name__))
より多くのタイプをサポートする場合は、functools.singledispatch
(Python 3.7)を使用します。
@functools.singledispatch
def make_hashable(x):
raise TypeError("Don't know how to make {} objects hashable".format(type(x).__name__))
@make_hashable.register
def _(x: collections.abc.Hashable):
return x
@make_hashable.register
def _(x: collections.abc.Sequence):
return tuple(make_hashable(xi) for xi in x)
@make_hashable.register
def _(x: collections.abc.Set):
return frozenset(make_hashable(xi) for xi in x)
@make_hashable.register
def _(x: collections.abc.Mapping):
return frozendict({k: make_hashable(v) for k, v in x.items()})
# add your own types here
dict
のDataFrame
オブジェクト。
elif
で動作するように次の句を追加しました。 回答を編集して、受け入れるかどうかを確認します...DataFrame
elif isinstance(x, pd.DataFrame): return make_hashable(hash_pandas_object(x).tolist())
hash
ランダム化は、Python 3.7でデフォルトで有効になっている意図的なセキュリティ機能です。
これを行うには、マップライブラリを使用できます。具体的には、maps.FrozenMap
import maps
fm = maps.FrozenMap(my_dict)
hash(fm)
をインストールするにはmaps
、次のようにします。
pip install maps
ネストされたdict
ケースも処理します。
import maps
fm = maps.FrozenMap.recurse(my_dict)
hash(fm)
免責事項:私はmaps
図書館の作者です。
.recurse
ます。maps.readthedocs.io/en/latest/api.html#maps.FrozenMap.recurseをご覧ください。リストの順序付けは意味的に意味があります。順序に依存しないようにする場合は、を呼び出す前にリストをセットに変換できます.recurse
。また、使用することができますlist_fn
し、パラメータを.recurse
とは異なるハッシュ可能なデータ構造を使用するtuple
(.eg frozenset
)
私はそれをこのようにします:
hash(str(my_dict))
hash(str({'a': 1, 'b': 2})) != hash(str({'b': 2, 'a': 1}))
一部のディクショナリでは機能する可能性がありますが、すべてで機能するとは限りません。