本日は、Pythonに内蔵されている hash() 関数についてご紹介します。
Pythonの hash() 関数はPythonオブジェクトのハッシュ値を計算する関数です。
しかし、この言語ではこれを大々的に利用しています。
いくつかの例を使って、この関数についてもっと理解しましょう!
Python の基本的な構文 hash()
この関数は、Pythonの不変オブジェクトを受け取り、このオブジェクトのハッシュ値を返します。
value = hash(object)
 | 
ハッシュ値は hash() が内部で呼び出すハッシュ関数 (__hash__()) に依存していることを覚えておいてください。
このハッシュ関数は、ほぼランダムな分布が得られるような十分なものである必要があります。
では、なぜハッシュ関数にこれほどまでに値のランダム性を求めるのでしょうか。
それは、ハッシュ関数がほとんどすべてのキーを一意な値に対応付けるようにしたいからです。
値がランダムに分布していれば、2つの異なるキーが同じ値にマップされる可能性はほとんどありません。
では、整数、浮動小数点数、文字列のような単純なオブジェクトに対して hash() 関数を使用してみましょう。
hash() 関数の使用 – いくつかの例
int_hash = hash(1020)
float_hash = hash(100.523)
string_hash = hash("Hello from Python")
print(f"For {1020}, Hash : {int_hash}")
print(f"For {100.523}, Hash: {float_hash}")
print(f"For {'Hello from Python'}, Hash: {string_hash}")
 | 
結果は以下の通りです。
For 1020, Hash : 1020
For 100.523, Hash: 1205955893818753124
For Hello from Python, Hash: 5997973717644023107
 | 
ご覧のように、整数は元の値と同じハッシュ値を持ちます。
しかし、float や string オブジェクトは明らかに異なる値になっています。
さて、同じオブジェクト(integer/floatを除く)が常に同じハッシュ値を持っていると、あまり安全ではありません。
例えば、同じスニペットを2回目に実行したときの出力がこれです。
For 1020, Hash : 1020
For 100.523, Hash: 1205955893818753124
For Hello from Python, Hash: -7934882731642689997
 | 
見ての通り、文字列の値が変わっていますね! これは良いことで、同じオブジェクトに誰かがアクセスするのを防ぐことができます。
ハッシュの値が一定になるのは、プログラムの実行時間内だけです。
その後、再びプログラムを実行するたびに変化し続けます。
なぜミュータブルなオブジェクトにhash()が使えないのか?
さて、先ほど hash() は不変のオブジェクトにしか使えないという話をしたのを思い出してください。
つまり、リストやセット、辞書などのようなミュータブルなオブジェクトでは hash() は使えないということです。
print(hash([1, 2, 3]))
 | 
結果は以下の通りです。
TypeError: unhashable type: 'list'
 | 
なぜこのようなことが起こるのでしょうか?さて、Mutableオブジェクトの値が変わるたびにハッシュ値を変更し続けるのは、プログラムにとって面倒なことでしょう。
これでは、ハッシュ値を再び更新し続けるのに非常に時間がかかってしまいます。
このため、 hash() を使って mutable オブジェクトをハッシュ化することはできません。
なぜなら、mutable オブジェクトは1つの値しか持たず、その値は我々からは見えないため、プログラムが内部でその値への参照を保持することができるからです。
しかし、イミュータブルなタプルに対しては hash() を使うことができます。
これは、intやfloatなどの不変なオブジェクトのみから構成されるタプルです。
>>> print(hash((1, 2, 3)))
2528502973977326415>>> print(hash((1, 2, 3, "Hello")))
-4023403385585390982
>>> print(hash((1, 2, [1, 2])))
Traceback (most recent call last):  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
 | 
カスタムオブジェクトでhash()を使用する
Python のデフォルトの hash() 実装は __hash__() メソッドをオーバーライドすることで動作するので、関連する属性が不変であれば、 __hash__() をオーバーライドして独自の hash() メソッドをカスタムオブジェクト用に作成することが可能です。
それでは、クラス Student を作ってみましょう。
ここでは、__hash__() メソッドをオーバーライドして、関連する属性に対して hash() を呼び出すことにします。
また、__eq__() メソッドを実装して、2つのカスタムオブジェクトが等価であることをチェックします。
class Student:
    def __init__(self, name, id):
        self.name = name
        self.id = id
    def __eq__(self, other):
        # Equality Comparison between two objects
        return self.name == other.name and self.id == other.id
    def __hash__(self):
        # hash(custom_object)
        return hash((self.name, self.id))
student = Student('Amit', 12)
print("The hash is: %d" % hash(student))
# We'll check if two objects with the same attribute values have the same hashstudent_copy = Student('Amit', 12)
print("The hash is: %d" % hash(student_copy))
 | 
結果は以下の通りです。
The hash is: 154630157590
The hash is: 154630157597
 | 
確かにカスタムオブジェクトのハッシュを観察することができます。
これはまさに私たちがハッシュ関数に期待することであり、 hash() はそれを見事に実現してくれました!
この記事もチェック:Pythonでメモ化の実装|関数やクラス、デコレーターを使った方法を解説
まとめ
Pythonの hash() 関数の使用について学びました。
これは、プログラムが特別な整数値を使って各オブジェクトへの参照を保持するために非常に便利です。
また、カスタムオブジェクトの属性が不変である場合に、 hash() をどのように動作させるかについて学びました。