web-dev-qa-db-ja.com

ハッシュ可能、不変

最近のSO質問から( リストでインデックス付けされたpythonに辞書を作成する を参照) Pythonの不変オブジェクト。

  • Hashableは実際にはどういう意味ですか?
  • ハッシュ可能と不変の関係は何ですか?
  • ハッシュ可能な可変オブジェクトまたはハッシュ不可能な不変オブジェクトはありますか?
76
joaquin

ハッシング は、一定の時間(O(1))。これは、高性能アルゴリズムとデータ構造にとって重要です。

Immutability は、オブジェクトが作成された後、特にそのオブジェクトのハッシュ値を変更する可能性がある何らかの方法で、重要な方法で変更されないという考え方です。

ハッシュキーとして使用されるオブジェクトは、通常、ハッシュ値が変化しないように不変でなければならないため、2つのアイデアは関連しています。変更が許可された場合、ハッシュテーブルなどのデータ構造内のそのオブジェクトの場所が変更され、効率化のためのハッシュの目的全体が無効になります。

アイデアを本当に把握するには、C/C++などの言語で独自のハッシュテーブルを実装するか、HashMapクラスのJava実装を読んでください。

84
maerics
  • ハッシュ可能な可変オブジェクトまたはハッシュ不可能な不変オブジェクトはありますか?

Pythonでは、Tupleは不変ですが、すべての要素がハッシュ可能な場合にのみハッシュ可能です。

>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
TypeError: unhashable type: 'list'

ハッシュ可能型

  • アトミック不変タイプは、str、byte、numericタイプなど、すべてハッシュ可能です
  • 凍結セットは常にハッシュ可能です(その要素は定義によりハッシュ可能でなければなりません)
  • タプルは、すべての要素がハッシュ可能な場合にのみハッシュ可能です
  • ユーザー定義型は、ハッシュ値がid()であるため、デフォルトでハッシュ可能です
11
liuyihe

技術的には、ハッシュ可能とは、クラスが__hash__()を定義することを意味します。ドキュメントによると:

__hash__()は整数を返す必要があります。唯一の必須プロパティは、等しいと比較するオブジェクトが同じハッシュ値を持つことです。オブジェクトの比較においても役割を果たすオブジェクトのコンポーネントのハッシュ値を何らかの方法で(たとえば、排他的ORを使用して)混ぜることをお勧めします。

Python組み込み型については、すべてのハッシュ可能な型も不変です。

__hash__()を定義した可変オブジェクトを作成することは困難ですが、おそらく不可能ではありません。

8
Andrew Jaffe

Python用語集 から:

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

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

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

辞書とセットは、ハッシュテーブルでの効率的な検索のためにハッシュを使用する必要があります。ハッシュを変更するとデータ構造が台無しになり、dictまたはsetが失敗するため、ハッシュ値は不変でなければなりません。ハッシュ値を不変にする最も簡単な方法は、オブジェクト全体を不変にすることです。そのため、この2つがよく一緒に言及されます。

組み込みの可変オブジェクトはいずれもハッシュ可能ではありませんが、not可変のハッシュ値を持つ可変オブジェクトを作成することは可能です。オブジェクトの一部のみがそのIDを表すのが一般的ですが、オブジェクトの残りの部分には自由に変更できるプロパティが含まれています。ハッシュ値と比較関数がIDに基づいており、可変プロパティではなく、IDが変わらない限り、要件は満たされています。

7
Mark Ransom

間の相互作用により、不変とハッシュ可能の間に明示的な関係が存在しない場合でも、暗黙的です

  1. 等しいと比較するハッシュ可能なオブジェクトは、同じハッシュ値を持たなければなりません
  2. オブジェクトは、その存続期間中に変更されないハッシュ値を持つ場合、ハッシュ可能です。

ここでは、__eq__を再定義しない限り問題はありません。したがって、オブジェクトクラスは値の等価性を定義します。

それが終わったら、同じ値を表すオブジェクトに対して常に同じ値を返す安定したハッシュ関数を見つける必要があります(たとえば、__eq__)はTrueを返し、オブジェクトの存続期間中は決して変化しません。

これが可能なアプリケーションを見ることは困難です。可能なクラスを考えてください[〜#〜] a [〜#〜]これらの要件を満たしています。 __hash__が定数を返す明らかな縮退ケースがありますが。

今:-

>>> a = A(1)
>>> b = A(1)
>>> c = A(2)
>>> a == b
True
>>> a == c
False
>>> hash(a) == hash(b)
True
>>> a.set_value(c)
>>> a == c
True
>>> assert(hash(a) == hash(c)) # Because a == c => hash(a) == hash(c)
>>> assert(hash(a) == hash(b)) # Because hash(a) and hash(b) have compared equal 
                                 before and the result must stay static over the objects lifetime.

実際、これは作成時にhash(b)== hash(c)を意味しますが、同等に比較されることはありません。とにかく、値による比較を定義する可変オブジェクトに対して__hash__()を便利に定義するのに苦労しています。

__lt____le____gt__および__ge__の比較は影響を受けないため、ハッシュ可能なオブジェクトの順序を変更可能、またはそれ以外の場合は、その値に基づきます。

4
rgammans

これがGoogleのトップヒットであるという理由だけで、可変オブジェクトをハッシュ可能にする簡単な方法を次に示します。

>>> class HashableList(list):
...  instancenumber = 0  # class variable
...  def __init__(self, initial = []):
...   super(HashableList, self).__init__(initial)
...   self.hashvalue = HashableList.instancenumber
...   HashableList.instancenumber += 1
...  def __hash__(self):
...   return self.hashvalue
... 
>>> l = [1,2,3]
>>> m = HashableList(l)
>>> n = HashableList([1,2,3])
>>> m == n
True
>>> a={m:1, n:2}
>>> a[l] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> m.hashvalue, n.hashvalue
(0, 1)

実際に、SQLAlchemyレコードを可変でより便利なものにキャストするクラスを作成し、dictキーとして使用するためのハッシュ可能性を維持するときに、このような用途を見つけました。

3
jcomeau_ictx

不変とは、オブジェクトの存続期間中にオブジェクトが大幅に変更されないことを意味します。あいまいではありますが、プログラミング言語では一般的な考え方です。

ハッシュ可能性はわずかに異なり、比較を指します。

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

すべてのユーザー定義クラスには __hash__ メソッド。デフォルトではオブジェクトIDのみを返します。したがって、ハッシュ可能性の基準を満たすオブジェクトは必ずしも不変ではありません。

宣言する新しいクラスのオブジェクトは、たとえば__hash__

不変オブジェクトはすべてハッシュ可能であると言えます。なぜなら、オブジェクトの存続期間中にハッシュが変更されると、オブジェクトが変化したことを意味するからです。

しかし、そうではありません。リスト(可変)を持つタプルを考えます。タプルは不変であると言う人もいますが、同時にハッシュ可能ではありません(スロー)。

d = dict()
d[ (0,0) ] = 1    #perfectly fine
d[ (0,[0]) ] = 1  #throws

ハッシュ可能性と不変性は、型ではなくオブジェクトインスタンスを指します。たとえば、タプル型のオブジェクトはハッシュ可能またはそうでない場合があります。

3
user2622016

In Pythonそれらはほとんど交換可能です;ハッシュはコンテンツを表すことになっているので、オブジェクトと同じように変更可能であり、オブジェクトにハッシュ値を変更させると、 dictキー。

他の言語では、ハッシュ値はオブジェクトの「アイデンティティ」に関連しており、(必ずしも)値​​には関連していません。したがって、可変オブジェクトの場合、ポインターを使用してハッシュを開始できます。もちろん、オブジェクトがメモリ内で移動しないと仮定します(一部のGCのように)。これは、たとえば、Luaで使用されるアプローチです。これにより、可変オブジェクトがテーブルキーとして使用可能になります。しかし、初心者にはいくつかの(不快な)驚きをもたらします。

最終的に、不変のシーケンス型(タプル)を持つことは、「複数値キー」にとってより良いものになります。

1
Javier

ハッシュ可能とは、変数の値を定数(文字列、数値など)で表現(または、エンコード)できることを意味します。したがって、可変の変数はハッシュ化できません。また、同じトークンで、不変の変数のみがハッシュ化できます。

お役に立てれば ...

0
ktdrv