124

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

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

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

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

4

11 に答える 11

47

Python wikiのトピックに関する優れた記事があります:リストが辞書キーになれない理由。そこで説明されているように:

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

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

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

于 2011-08-31T13:36:23.740 に答える
44

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

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

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

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

于 2011-08-31T13:55:05.080 に答える
34

リストをタプルに変更し、それをキーとして使用できることがわかりました。

d = {tuple([1,2,3]): 'value'}
于 2018-08-02T18:44:59.600 に答える
18

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

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

何をd[li]返す必要がありますか?同じリストですか?どうd[[1,2,3]]ですか?値は同じですが、リストが異なりますか?

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

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

于 2011-08-31T13:32:58.033 に答える
9

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

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

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

辞書ルックアップでリストリテラルを使用するのはどうですか?

于 2011-08-31T13:31:07.260 に答える
6

リストは変更可能であるため、dictキー(およびメンバー)はハッシュ可能である必要があります。ハッシュ値はインスタンス属性に基づいて計算する必要setがあるため、変更可能オブジェクトをハッシュすることはお勧めできません。

この回答では、いくつかの具体的な例を示し、できれば既存の回答に付加価値を付けます。すべての洞察は、データ構造の要素にも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でそれを見つけることができなくなりました。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

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

>>> 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(または)を使用して適切なインスタンスを見つけるには、dictのキーitem in my_setにあるインスタンスと同じ数の等価性チェックを実行する必要がありstupidlist3ます(最悪の場合)。この時点で、辞書の目的であるO(1)ルックアップは完全に無効になります。これは、次のタイミングで示されます(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ですが、ハッシュ衝突の負荷がないセットでは、予想される超高速のルックアップ時間(係数500)があります。


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

于 2018-10-25T22:18:24.837 に答える
3

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

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

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

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

于 2011-08-31T13:36:57.053 に答える
3

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

ちなみに、dictobjectsルックアップの実際の実装は、KnuthVol。のアルゴリズムDに基づいています。3、秒 6.4。その本を利用できる場合は、読む価値があるかもしれません。さらに、本当に興味がある場合は、ここでdictobjectの実際の実装に関する開発者のコ​​メントを確認することをお勧めします。それがどのように機能するかについては、非常に詳細に説明されています。あなたが興味を持っているかもしれない辞書の実装についてのPython講義もあります。それらは最初の数分でキーの定義とハッシュが何であるかを通り抜けます。

于 2011-08-31T13:54:20.673 に答える
-1

Python 2.7.2のドキュメントによると:

オブジェクトは、その存続期間中に変更されないハッシュ値(メソッドが必要)を持っている場合はハッシュ可能であり、他のオブジェクト(またはメソッド__hash__()が必要)と比較できます。等しいと比較するハッシュ可能なオブジェクトは、同じハッシュ値を持っている必要があります。__eq__()__cmp__()

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

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

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

リストハッシュにidを使用すると、すべてのリストの比較が異なることを意味します。これは、驚くべきことであり、不便です。

于 2011-08-31T13:44:11.937 に答える
-1

ディクショナリは、キーのマップ、ハッシュされた新しいキーに変換された値、および値のマッピングを格納するHashMapです。

(疑似コード)のようなもの:

{key : val}  
hash(key) = val

辞書のキーとして使用できるオプションがどれか疑問に思っている場合。それで

ハッシュ可能(ハッシュに変換でき、静的な値を保持できる、つまり上記のようにハッシュキーを作成するために不変)であるものはすべて適格ですが、リストまたはセットオブジェクトは外出先で変更できるため、hash(key)も必要です。リストまたはセットと同期するためだけに変更します。

あなたが試すことができます :

hash(<your key here>)

正常に機能する場合は、辞書のキーとして使用するか、ハッシュ可能なものに変換することができます。


要するに :

  1. そのリストをに変換しますtuple(<your list>)
  2. そのリストをに変換しますstr(<your list>)
于 2020-03-19T04:03:14.177 に答える
-1

単純に、キーは不変dictである必要があることを覚えておくことができます(正確には、ハッシュ可能)。リストは変更可能です(正確には、リストは有効なメソッドを提供しません)。__hash__

ここで、不変オブジェクト(不変オブジェクト)とは、作成後に状態を変更できないオブジェクトのことです。これは、作成後に変更できる可変オブジェクト(変更可能オブジェクト)とは対照的です。

于 2020-07-23T15:42:02.957 に答える