最近のSOの質問(リストでインデックス付けされたPythonでの辞書の作成を参照)から、Pythonでのハッシュ可能オブジェクトと不変オブジェクトの意味について間違った概念を持っている可能性があることに気付きました。
- 実際のハッシュ可能とはどういう意味ですか?
- ハッシュ可能と不変の関係は何ですか?
- ハッシュ可能である可変オブジェクトまたはハッシュ可能ではない不変オブジェクトはありますか?
最近のSOの質問(リストでインデックス付けされたPythonでの辞書の作成を参照)から、Pythonでのハッシュ可能オブジェクトと不変オブジェクトの意味について間違った概念を持っている可能性があることに気付きました。
回答:
ハッシュとは、大量のデータを繰り返し可能な方法で非常に少量(通常は単一の整数)に変換して、テーブルで一定時間(O(1)
)で検索できるようにするプロセスです。これは、高性能にとって重要です。アルゴリズムとデータ構造。
不変性とは、オブジェクトが作成された後、特にそのオブジェクトのハッシュ値を変更する可能性のある方法で、オブジェクトが重要な方法で変更されないという考え方です。
ハッシュキーとして使用されるオブジェクトは通常、ハッシュ値が変更されないように不変でなければならないため、2つのアイデアは関連しています。変更が許可された場合、ハッシュテーブルなどのデータ構造内のそのオブジェクトの場所が変更され、効率を高めるためのハッシュの目的全体が無効になります。
アイデアを実際に理解するには、C / C ++などの言語で独自のハッシュテーブルを実装するか、HashMap
クラスのJava実装を読む必要があります。
object
するカスタムクラスから作成されたインスタンスはハッシュ可能ですが、不変ではありません。これらのインスタンスはdictのキーを持って使用できますが、渡されれば変更できます。
- ハッシュ可能である可変オブジェクトまたはハッシュ可能ではない不変オブジェクトはありますか?
Pythonでは、タプルは不変ですが、すべての要素がハッシュ可能である場合にのみハッシュ可能です。
>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
TypeError: unhashable type: 'list'
ハッシュ可能なタイプ
Python用語集から:
オブジェクトは、その存続期間中に変更されないハッシュ値を持っている場合(
__hash__()
メソッドが必要)、ハッシュ可能であり、他のオブジェクトと比較できます(__eq__()
または__cmp__()
メソッド)です。等しいと比較するハッシュ可能なオブジェクトは、同じハッシュ値を持っている必要があります。これらのデータ構造は内部でハッシュ値を使用するため、ハッシュ可能性により、オブジェクトは辞書キーおよびセットメンバーとして使用可能になります。
Pythonの不変の組み込みオブジェクトはすべてハッシュ可能ですが、変更可能なコンテナー(リストや辞書など)はハッシュ可能ではありません。ユーザー定義クラスのインスタンスであるオブジェクトは、デフォルトでハッシュ可能です。それらはすべて等しくなく比較され、それらのハッシュ値はそれらのid()です。
ディクトとセットは、ハッシュテーブルで効率的にルックアップするためにハッシュを使用する必要があります。ハッシュを変更するとデータ構造が台無しになり、dictまたはsetが失敗するため、ハッシュ値は不変である必要があります。ハッシュ値を不変にする最も簡単な方法は、オブジェクト全体を不変にすることです。そのため、この2つはよく一緒に言及されます。
組み込みの可変オブジェクトはいずれもハッシュ可能ではありませんが、可変ではないハッシュ値を使用して可変オブジェクトを作成することは可能です。オブジェクトの一部のみがそのアイデンティティを表すのが一般的ですが、オブジェクトの残りの部分には自由に変更できるプロパティが含まれています。ハッシュ値と比較関数がIDに基づいており、可変プロパティに基づいておらず、IDが変更されない限り、要件は満たされています。
id
)。これはオブジェクトの存続期間中は変更できないため、ハッシュ可能ですが、可変タイプを定義できないという意味ではありません。申し訳ありませんが、ハッシュ可能性は不変性を意味するものではありません。
技術的には、ハッシュ可能とは、クラスがを定義することを意味します__hash__()
。ドキュメントによると:
__hash__()
整数を返す必要があります。唯一必要なプロパティは、等しいと比較するオブジェクトが同じハッシュ値を持つことです。オブジェクトの比較においても役割を果たすオブジェクトのコンポーネントのハッシュ値を何らかの方法で(排他的論理和を使用するなどして)混合することをお勧めします。
Pythonの組み込み型の場合、ハッシュ可能な型もすべて不変だと思います。
それでも定義されている可変オブジェクトを持つことは困難ですが、おそらく不可能ではありません__hash__()
。
__hash__
オブジェクトのid
;を返すようにデフォルトで定義されていることは注目に値します。__hash__ = None
ハッシュ化できないように設定するには、邪魔にならないようにする必要があります。また、Mark Ransomが言及しているように、ハッシュ値が決して変更できない場合にのみハッシュ可能であるという追加の条件があります。
list
定義__hash__
という意味でhasattr([1,2,3], "__hash__")
リターンがTrue
、しかし、呼び出しhash([1,2,3])
昇給をTypeError
、それはまさにハッシュ可能ではありませんので、(Pythonの3)。の存在に依存__hash__
するだけでは、何かがa)ハッシュ可能b)不変であるかどうかを判断するのに十分ではありません
相互作用のために不変とハッシュ可能の間に強制された明示的な関係がない場合でも、暗黙的です
__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__
あなたはまだ自分の価値に基づいて可変あるいは、ハッシュ可能オブジェクトの順序を定義することができるようにcomparsionsは影響を受けません。
これが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キーとして使用するためのハッシュ機能を維持しながら、このような用途を実際に見つけました。
不変とは、オブジェクトがその存続期間中に重要な方法で変更されないことを意味します。これは、プログラミング言語では漠然としていますが一般的な考え方です。
ハッシュ性は若干異なり、比較を指します。
hashableオブジェクトは、その存続期間中に変更されないハッシュ値(
__hash__()
メソッドが必要)を持ち、他のオブジェクトと比較できる(__eq__()
or__cmp__()
メソッドが必要)場合にハッシュ可能です。等しいと比較するハッシュ可能なオブジェクトは、同じハッシュ値を持っている必要があります。
すべてのユーザー定義クラスには __hash__
メソッドがあり、デフォルトではオブジェクトIDを返すだけです。したがって、ハッシュ可能性の基準を満たすオブジェクトは、必ずしも不変であるとは限りません。
宣言した新しいクラスのオブジェクトは、たとえばからスローすることによって防止しない限り、辞書キーとして使用できます。 __hash__
オブジェクトの存続期間中にハッシュが変更された場合、それはオブジェクトが変更されたことを意味するため、すべての不変オブジェクトはハッシュ可能であると言えます。
しかし、完全ではありません。リスト(可変)を持つタプルについて考えてみます。タプルは不変であると言う人もいますが、同時にハッシュ可能ではありません(スロー)。
d = dict()
d[ (0,0) ] = 1 #perfectly fine
d[ (0,[0]) ] = 1 #throws
ハッシュ可能性と不変性は、タイプではなくオブジェクトインスタンスを参照します。たとえば、タプル型のオブジェクトはハッシュ可能かどうかを指定できます。
comparison != identity
のように一緒に「無効」の値を比較しているfloat("nan") == float("nan")
、またはスライスからのインターンの文字列:"apple" is "apple"
対"apple" is "crabapple"[4:]
Pythonでは、ほとんど互換性があります。ハッシュはコンテンツを表すことになっているため、オブジェクトと同じように変更可能であり、オブジェクトにハッシュ値を変更させると、dictキーとして使用できなくなります。
他の言語では、ハッシュ値はオブジェクトの「アイデンティティ」に関連しており、(必然的に)値に関連していません。したがって、可変オブジェクトの場合、ポインターを使用してハッシュを開始できます。もちろん、オブジェクトがメモリ内で移動しないと仮定します(一部のGCのように)。これは、たとえばLuaで使用されているアプローチです。これにより、可変オブジェクトがテーブルキーとして使用できるようになります。しかし、初心者にとってはいくつかの(不快な)驚きを生み出します。
結局、不変のシーケンスタイプ(タプル)を持つことで、「複数値キー」に適しています。
HashMap
キーとして使用されているオブジェクトを変更すると、が壊れてしまいます。マップを印刷しても、古いキーも新しいキーも見つかりません。