Pythonでは、多くの要素を持つタプルがある場合、そのハッシュはその要素のid
sまたはその要素のコンテンツから計算されますか?
この例では、
a = (1, [1,2])
hash(a)
リストがハッシュできないとエラーになります。したがって、idによって計算されたものではないか、おそらく要素が変更可能かどうかのチェックがあると思います。
この例をご覧ください
class A: pass
a0 = A()
ta = (1, a0)
hash(ta) # -1122968024
a0.x = 20
hash(ta) # -1122968024
ここで、ta
のハッシュは、その要素、つまりa0
。かもね a0
のIDはハッシュ計算に使用されますか? a0
どういうわけか不変と見なされますか? python型が変更可能かどうかをどのように知るのですか?
今、このケースを検討してください
b = (1, 2)
id(b) # 3980742764
c = (1, 2)
id(c) # 3980732588
tb = (1, b)
tc = (1, c)
hash(tb) # -1383040070
hash(tc) # -1383040070
b
とc
の内容がハッシュ計算に使用されているようです。
これらの例をどのように理解すればよいですか?
どちらでもない。これは、「コンテンツ」(値/属性)ではなく、これらの要素のハッシュに基づいて計算されます。
pythonのドキュメント用語集 のこの段落をご覧ください。
isハッシュ可能かどうか、およびhowハッシュされるかどうかは、その.__hash__()
メソッドの実装に依存します。 Python自体はオブジェクトの可変性については考えていません。
最初の例では、Tuple
は要素に基づいて自身をハッシュしますが、list
にはハッシュがまったくありません-.__hash__()
メソッドは実装されていません(および正当な理由)。そのため、内部にTuple
オブジェクトを持つlist
はハッシュ可能ではありません。
それを念頭に置いて、 python data model documentation と、それがトピックについて何を言っているかを見てみましょう:
ユーザー定義クラスには、デフォルトで
__eq__()
および__hash__()
メソッドがあります。それらを使用して、すべてのオブジェクトは等しくない(それ自体を除く)を比較し、x.__hash__()
はx == y
とx is y
とhash(x) == hash(y)
の両方を意味する適切な値を返します。
クラスの.__hash__()
を定義する必要がないのはこのためです-pythonはこの場合それを行います。デフォルトの実装では、インスタンスフィールドは考慮されません。ハッシュを変更せずにオブジェクト内の値。
この点であなたは正しいです-カスタムクラスのハッシュ関数のデフォルト(CPython's)実装は、内部の値ではなく、オブジェクトの id()
に依存していますそれ。これは実装の詳細であり、Pythonバージョン間では異なります。Pythonの最近のバージョンでは、hash()
とid()
の関係はランダム化を伴います。
詳細はかなり複雑で、おそらくいくつかの高度な数学を伴いますが、タプルオブジェクトのハッシュ関数の実装はCで記述されており、 here (static Py_hash_t tuplehash(PyTupleObject *v)
を参照)で見ることができます。
計算には、定数と各タプルの要素のハッシュとのXOR演算が含まれます。要素のハッシュを担当する行は次のとおりです。
y = PyObject_Hash(*p++);
したがって、元の質問に答えるために、XOR hokus-pocusとその要素のそれぞれのハッシュ)の束を行います。これらの要素の内容が使用されるかどうかは、特定のハッシュ関数に依存します。
ハッシュの基本的な規約は、等しいオブジェクトは等しいハッシュを持つです。特に、ハッシュは、可変性または突然変異を直接気にしません。 等価比較に影響する突然変異のみを考慮します。
ネストされたリストを変更すると、等価比較でのタプルの動作が変わるため、最初のタプルはハッシュできません。
ミューティングa0
の2番目の例では、等価比較に影響しないため、タプルのハッシュには影響しません。 a0
はそれ自体と同じであり、そのハッシュは変更されません。
3番目の例のtb
とtc
は、要素が同じオブジェクトであるかどうかに関係なく、等しいタプルであるため、等しいハッシュを持っています。
これはすべて、タプルが(直接)ハッシュにid
を使用できないことを意味します。もしそうなら、明確だが等しい要素を持つ等しいタプルは異なるハッシュをすることができ、ハッシュの規約に違反します。特別なケースの要素タイプがない場合、タプルが独自のハッシュを計算するために使用できるのは要素のハッシュだけなので、タプルは要素のハッシュに基づいてハッシュを作成します。
「タプルのハッシュはアイデンティティまたは値に基づいて計算されますか?」という質問に対する答えどちらでもない。
正解は、タプルのハッシュが要素のハッシュから計算されるということです。 thoseハッシュの計算方法は(多かれ少なかれ)無関係です。
これを証明する簡単な方法は、リストをタプルに入れたときに何が起こるかを確認することです:
>>> hash( (1, 2) )
3713081631934410656
>>> hash( (1, []) )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
リストはハッシュ化できないため、リストを含むタプルもハッシュ化できません。
持ってきたこの例を詳しく見てみましょう。
class A: pass
a0 = A()
ta = (1, a0)
hash(ta) # -1122968024
a0.x = 20
hash(ta) # -1122968024
なぜa0.x = 20
を設定してもタプルのハッシュに影響しないのですか? a0
のハッシュを出力するようにこのコードを変更すると、a0.x = 20
の設定はa0
のハッシュ値に影響を与えないことがわかります。
a0 = A()
print(hash(a0)) # -9223363274645980307
a0.x = 20
print(hash(a0)) # -9223363274645980307
これは、pythonがデフォルトのハッシュ関数を実装しているためです。From ドキュメント :
ユーザー定義クラスには、デフォルトで
__eq__()
および__hash__()
メソッドがあります。それらを使用すると、すべてのオブジェクトは等しくない(それ自体を除く)を比較し、x.__hash__()
はx == y
がx is y
とhash(x) == hash(y)
の両方を意味する適切な値を返します。
デフォルトのハッシュ関数はオブジェクトの属性を無視し、オブジェクトのIDに基づいてハッシュを計算します。 a0
にどのような変更を加えても、そのハッシュは常に同じままです。 (カスタム __hash__
メソッドを実装することにより、A
クラスのインスタンスにカスタムハッシュ関数を定義することは可能です。)
補遺:リストがハッシュ可能でない理由は、リストが可変であるためです。 ドキュメント から:
クラスが可変オブジェクトを定義し、
__eq__()
メソッドを実装する場合、__hash__()
を実装するべきではありません。ハッシュ可能コレクションの実装では、キーのハッシュ値が不変である必要があるためです(オブジェクトのハッシュ値が変更された場合、間違ったハッシュバケットに入れられます)。
リストはこのカテゴリに分類されます。
Tuple
のハッシュは、タプルの_id_sではなく、contentsに基づいています。また、ハッシュは再帰的に計算されます。1つの要素が(list
要素のように)ハッシュ可能でない場合、Tuple自体はハッシュ可能ではありません。
a
とb
がタプルとa == b
である場合、a is not b
であっても、hash(a) == hash(b)
(もちろんハッシュを計算できる場合)は完全に正常です。
(それどころかhash(a) == hash(b)
はa == b
を意味するものではありません)
is
によって伝えられる情報は、たとえばpythonオブジェクトのインターンが原因で非常に有用ではありません。