web-dev-qa-db-ja.com

リストをPythonのdictキーとして使用できないのはなぜですか?

python dict。のキーとして使用できる/使用できないものについて少し混乱しています。

dicked = {}
dicked[None] = 'foo'     # None ok
dicked[(1,3)] = 'baz'    # Tuple ok
import sys
dicked[sys] = 'bar'      # wow, even a module is ok !
dicked[(1,[3])] = 'qux'  # oops, not allowed

タプルは不変型ですが、その中にリストを非表示にすると、キーになりません。モジュール内にリストを簡単に非表示にできませんか?

キーは「ハッシュ可能」でなければならないという漠然とした考えがありましたが、技術的な詳細については自分の無知を認めるつもりです。ここで実際に何が起こっているのか分かりません。リストをキーとして、たとえばハッシュをメモリの場所として使用しようとした場合、何が問題になるでしょうか?

80
wim

Python wiki: Why Lists Ca n't Can Dictionary Keys 。)のトピックに関する良い記事があります。

リストをキーとして、たとえばハッシュをメモリの場所として使用しようとした場合、何が問題になるでしょうか?

実際に要件を壊すことなく実行できますが、予期しない動作につながります。リストは、一般的に、その値がコンテンツの値から導出されたものとして扱われます。たとえば、(同等の)チェックを行う場合です。多くの人は、当然のことながら、任意のリストを使用できると期待しています[1, 2]同じキーを取得するには、まったく同じリストオブジェクトを保持する必要があります。しかし、値としての検索は、キーとして使用されるリストが変更されるとすぐに中断し、IDによる検索では、まったく同じリストを保持する必要があります-これは、他の一般的なリスト操作には必要ありません(少なくとも私は考えることができません) )。

モジュールやobjectなどの他のオブジェクトは、とにかく(sysと呼ばれる2つの異なるモジュールオブジェクトを最後に持っていたのはいつか)、オブジェクトIDからはるかに大きな取引を行い、それによって比較されますとにかく。したがって、dictキーとして使用された場合に、それらがIDで比較されることは、それほど驚くことではありません。

21
user395760

リストをPythonのdictキーとして使用できないのはなぜですか?

>>> d = {repr([1,2,3]): 'value'}
{'[1, 2, 3]': 'value'}

(この質問に出くわして、それを回避する方法を探している人のために)

ここで他の人が説明したように、実際にはできません。ただし、実際にリストを使用する場合は、代わりにその文字列表現を使用できます。

25
Remi

問題は、タプルが不変であり、リストが不変であるということです。以下を考慮してください

d = {}
li = [1,2,3]
d[li] = 5
li.append(4)

どうしたら良い d[li]戻りますか?同じリストですか? d[[1,2,3]]?値は同じですが、リストは異なりますか?

最終的に、満足のいく答えはありません。たとえば、機能する唯一のキーが元のキーである場合、そのキーへの参照がない場合、値に再度アクセスすることはできません。他のすべての許可されたキーを使用すると、元のキーを参照せずにキーを作成できます。

私の提案の両方が機能する場合、同じ値を返す非常に異なるキーがありますが、これは少し驚くべきことです。元のコンテンツのみが機能する場合、リストは変更されるため、キーはすぐに破損します。

10
Eric Wilson

ListをTupleに変更し、それをキーとして使用できることがわかりました。

d = {Tuple([1,2,3]): 'value'}
7
Ningrong Ye

答えがあります http://wiki.python.org/moin/DictionaryKeys

リストをキーとして、たとえばハッシュをメモリの場所として使用しようとした場合、何が問題になるでしょうか?

同じ内容の異なるリストを検索すると、同じ内容のリストを比較すると同等であると示されても、異なる結果が生成されます。

辞書検索でリストリテラルを使用するのはどうですか?

7
bpgergo

あなたの天蓋はここで見つけることができます:

リストが辞書キーになれない理由

Pythonの初心者は、なぜ言語にタプルとリストタイプの両方が含まれているのに、タプルは辞書キーとして使用できるのに、リストは使用できないのか疑問に思うことがよくあります。最初にPython辞書がどのように機能するかを理解することによって説明するのが最適です。

ソースと詳細: http://wiki.python.org/moin/DictionaryKeys

2
AKjsd89

あなたの質問に対する簡単な答えは、クラスリストはメソッドを実装していないということですhashこれはキーとして使用したいオブジェクトに必要です辞書。ただし、hashが(コンテナのコンテンツに基づいて)Tupleクラスと同じ方法で実装されない理由は、リストがリストを編集するにはハッシュを再計算する必要があるため、リストは下にあるハッシュテーブル内の間違ったバケットに配置される可能性があります。タプル(不変)は変更できないため、この問題に遭遇しないことに注意してください。

補足として、dictobjectsルックアップの実際の実装は、Knuth Vol。のAlgorithm Dに基づいています。 3、秒6.4。あなたがその本を利用できるなら、読む価値があるかもしれませんし、実際に本当に興味があるなら、実際の開発者のコ​​メントを覗いてみたいかもしれません ここでのdictobjectの実装 =正確にどのように機能するかについては、詳細に説明します。また、興味のある辞書の実装に関する pythonレクチャー があります。キーの定義と最初の数分でハッシュとは何かを調べます。

1
Ben Wright

リストは変更可能であるため、dictキー(およびsetメンバー)はハッシュ可能である必要があり、ハッシュ値shouldはインスタンス属性の基礎。

この回答では、既存の回答の上に価値を追加することを願って、具体的な例をいくつか示します。すべての洞察は、setデータ構造の要素にも適用されます。

例1:ハッシュ値がオブジェクトの可変特性に基づいている可変オブジェクトのハッシュ。

_>>> class stupidlist(list):
...     def __hash__(self):
...         return len(self)
... 
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True
_

stupidを変更した後、ハッシュが変更されたため、辞書内でそれを見つけることができなくなりました。 dictのキーのリストに対する線形スキャンのみがstupidを見つけます。

例2:...しかし、なぜ単なるハッシュ値ではないのですか?

_>>> class stupidlist2(list):
...     def __hash__(self):
...         return id(self)
... 
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>> 
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False
_

等しいオブジェクトはdictまたはsetで見つけることができるように同じようにハッシュする必要があるため、これも良い考えではありません。

:... OK、すべてのインスタンスにわたる一定のハッシュはどうですか?!

_>>> class stupidlist3(list):
...     def __hash__(self):
...         return 1
... 
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>> 
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True
_

物事は期待通りに動作するように見えますが、何が起こっているのかを考えてください:クラスのすべてのインスタンスが同じハッシュ値を生成する場合、dictのキーとして3つ以上のインスタンスがある場合は常にハッシュ衝突が発生しますsetで。

_my_dict[key]_または_key in my_dict_(または_item in my_set_)で適切なインスタンスを見つけるには、dictのキーにある_stupidlist3_のインスタンスと同じ数のチェックを実行する必要があります(最悪の場合) )。この時点で、辞書の目的-O(1) lookup-)は完全に無効になります。これは、次のタイミングで実証されます(IPythonで行われます)。

例3のタイミング

_>>> lists_list = [[i]  for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>> 
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
_

ご覧のとおり、_stupidlists_set_のメンバーシップテストは、_lists_list_全体の線形スキャンよりもさらに遅くなりますが、ハッシュ衝突。


TL; DR:タプルは不変でハッシュ可能であるため、Tuple(yourlist)dictキーとして使用できます。

1
timgeb

Python 2.7.2ドキュメント:

オブジェクトは、その存続期間中に決して変化しないハッシュ値を持っている場合(hash()メソッドが必要です)、他のオブジェクトと比較できる場合、ハッシュ可能ですオブジェクト(eq()またはcmp()メソッドが必要です) 。等しいと比較するハッシュ可能なオブジェクトは、同じハッシュ値を持たなければなりません。

これらのデータ構造は内部的にハッシュ値を使用するため、ハッシュ可能性により、オブジェクトは辞書キーおよびセットメンバーとして使用可能になります。

Pythonの不変の組み込みオブジェクトはすべてハッシュ可能ですが、可変コンテナ(リストや辞書など)はありません。ユーザー定義クラスのインスタンスであるオブジェクトは、デフォルトでハッシュ可能です。それらはすべて等しくないものであり、ハッシュ値はid()です。

タプルは、その要素を追加、削除、または置換できないという意味で不変ですが、要素自体は変更可能です。リストのハッシュ値は要素のハッシュ値に依存するため、要素を変更するとリストのハッシュ値も変わります。

リストハッシュにIDを使用すると、すべてのリストが異なる方法で比較されることを意味し、驚くほど不便です。

0
Nicola Musatti