私は http://www.mypythonquiz.com を使用していますが、 question#45 は次のコードの出力を要求しています:
confusion = {}
confusion[1] = 1
confusion['1'] = 2
confusion[1.0] = 4
sum = 0
for k in confusion:
sum += confusion[k]
print sum
キー6
が1.0
を置き換えるため、出力は1
です。これは私にとって少し危険だと感じていますが、これは便利な言語機能ですか?
dict
は、データの表現方法ではなく、論理数値に応じてデータを保存することを目的としていることを考慮する必要があります。
int
sとfloat
sの違いは、実際には単なる実装の詳細であり、概念的なものではありません。理想的には、唯一の数値型は、サブユニティでさえ無限の精度を持つ任意の精度の数値でなければなりません...これはトラブルを起こすことなく実装するのが難しいです...しかし、それはPythonの唯一の将来の数値型になるかもしれません。
技術的な理由で異なるタイプを使用している間、Pythonはこれらの実装の詳細を隠そうとし、int
-> float
変換は自動的に行われます。
Pythonプログラムif x == 1: ...
は、x
が値1のfloat
である場合には取得されませんでした。
また、Python 3の値1/2
は0.5
(2つの整数の除算)およびlong
型と非Unicode文字列は、実装の詳細を隠そうと同じ試みで削除されました。
まず第一に:動作は hash 関数のドキュメントに明示的に文書化されています:
hash(object)
オブジェクトのハッシュ値(ある場合)を返します。ハッシュ値は整数です。辞書検索時に辞書キーをすばやく比較するために使用されます。 等しいと比較される数値は、ハッシュ値が同じです(
1
と1.0
の場合のように、異なるタイプであっても)。
第二に、ハッシュの制限が object.__hash__
のドキュメントで指摘されています
object.__hash__(self)
組み込み関数
hash()
によって呼び出され、set
、frozenset
、dict. __hash__()
などのハッシュされたコレクションのメンバーに対する操作に対して整数を返します。 唯一の必須プロパティは、等しいと比較するオブジェクトが同じハッシュ値を持つことです。
これはpythonに固有のものではありません。 Javaには同じ警告があります:hashCode
を実装する場合、物事が正しく機能するためには、must :x.equals(y)
はx.hashCode() == y.hashCode()
を意味します。
したがって、pythonは1.0 == 1
が成立することを決定したため、hash(1.0) == hash(1)
のようなhash
の実装を提供することはforcedです。副作用は1.0
と1
は、dict
キーとまったく同じように動作するため、動作します。
言い換えれば、動作自体を使用したり、何らかの方法で有用にする必要はありません。 必要です。その動作がなければ、誤って別のキーを上書きしてしまう可能性があります。
1.0 == 1
があったがhash(1.0) != hash(1)
があった場合でも、collisionが発生する可能性があります。また、1.0
と1
が衝突した場合、dict
は同じキーであるかどうかを確認するために平等を使用し、kaboomを使用したとしても値が上書きされます異なる。
これを回避する唯一の方法は、1.0 != 1
を使用することです。これにより、dict
が衝突の場合でもそれらを区別できるようになります。ただし、実際にはfloat
sとint
sを辞書キーとして使用することは決してないため、1.0 == 1
を使用することは、表示されている動作を回避することよりも重要であると見なされました。
pythonは、必要に応じて番号を自動的に変換することで数値の区別を隠そうとするため(例:1/2 -> 0.5
))このような状況でもこの動作が反映されることは理にかなっています。
この動作は、anyの実装で見られます。この実装では、キーの一致が比較に基づいて少なくとも部分的に(ハッシュマップのように)行われます。
たとえば、dict
が赤黒ツリーまたは他の種類のバランスの取れたBSTを使用して実装されている場合、キー1.0
が検索されると、他のキーとの比較は1
と同じ結果を返します。それでも同じように行動します。
ハッシュマップは、キーのエントリを見つけるために使用されるのはハッシュの値であり、比較はその後のみ行われるため、さらに注意が必要です。したがって、上記の規則に違反すると、dict
が期待どおりに動作するように見える場合や、サイズが変更された場合に、見つけるのが非常に難しいバグを導入することになります間違った動作を開始します。
これを修正する方法はであることに注意してください。辞書に挿入されたタイプごとに個別のハッシュマップ/ BSTを用意してください。この方法では、異なるタイプのオブジェクト間で衝突が発生することはありません。また、引数が異なるタイプである場合、==
の比較は重要ではありません。
ただし、これにより実装が複雑になりますが、アクセス時間をO(1))にするためにハッシュマップはかなりの数の空き場所を保持する必要があるため、おそらく非効率的です。複数のハッシュマップを作成することは、より多くのスペースを浪費することを意味し、キーの実際のルックアップを開始する前に、最初にどのハッシュマップを調べるかを選択する必要があります。
BSTを使用した場合、最初にタイプを検索し、2回目の検索を実行する必要があります。したがって、多くのタイプを使用する場合は、作業が2倍になります(そして、ルックアップはO(1)ではなくO(log n)を取ります)。
Pythonの場合:
1==1.0
True
これは暗黙的なキャストによるものです
しかしながら:
1 is 1.0
False
float
とint
間の自動キャストが便利な理由がわかります。int
をfloat
にキャストするのは比較的安全ですが、他の言語(例:暗黙のキャストを避けます。
それは実際には言語設計の決定であり、さまざまな機能よりも好みの問題です
辞書はハッシュテーブルで実装されます。ハッシュテーブルで何かを検索するには、ハッシュ値が示す位置から開始し、等しいキー値または空のバケットが見つかるまで別の場所を検索します。
等しいがハッシュが異なる2つのキー値がある場合、他のキー値が検索された場所にあったかどうかによって、一貫性のない結果が得られる可能性があります。たとえば、テーブルがいっぱいになるとこれが発生する可能性が高くなります。これは避けたいものです。 Python開発者はこれを念頭に置いていたようです。組み込みのhash
関数は、それらの値がint
かfloat
かに関係なく、同等の数値に対して同じハッシュを返すためです。他の数値型では、False
は0
に等しく、True
は1
に等しく、fractions.Fraction
およびdecimal.Decimal
でもこのプロパティを維持します。
a == b
の場合hash(a) == hash(b)
が object.__hash__()
の定義に文書化されているという要件
組み込み関数
hash()
によって呼び出され、set
、frozenset
、dict
などのハッシュされたコレクションのメンバーに対する操作のために呼び出されます。__hash__()
は整数を返す必要があります。唯一必要なプロパティは、等しいと比較するオブジェクトが同じハッシュ値を持つことです。オブジェクトの比較においても役割を果たすオブジェクトのコンポーネントのハッシュ値を何らかの方法で混合することをお勧めします(たとえば、排他的ORを使用するなど)。
TL; DR:比較したキーが同じ値にマッピングされない場合、辞書は壊れます。
率直に言って、反対は危険です! 1 == 1.0
。したがって、異なるキーをポイントさせ、評価された数値に基づいてそれらにアクセスしようとした場合、あいまいさを理解するのが難しいため、おそらくそれで問題が発生することを想像することはできません。
型は順応性があり(()非常に便利な機能)、したがってints
とfloats
distinctと同じ値の不必要なセマンティクスは、混乱を招くだけです。
私は、このコンテキストで1
と1.0
を同じものとして扱うのが理にかなっていることに他の人に同意します。 Pythonがそれらを異なる扱いをしたとしても、辞書の別個のキーとして1
と1.0
を使用しようとすることはおそらく悪い考えでしょう。 -キーのコンテキストで1.0
のエイリアスとして1
を使用するための自然なユースケースを考えるのに問題があります。問題は、キーがリテラルであるか、計算されることです。リテラルキーである場合、なぜ1
ではなく1.0
を使用しないのですか?計算されたキーである場合、丸め誤差により問題が発生する可能性があります。
>>> d = {}
>>> d[1] = 5
>>> d[1.0]
5
>>> x = sum(0.01 for i in range(100)) #conceptually this is 1.0
>>> d[x]
Traceback (most recent call last):
File "<pyshell#12>", line 1, in <module>
d[x]
KeyError: 1.0000000000000007
だから、一般的に言えば、あなたの質問への答えは「これはこれまで便利な言語機能ですか?」 「いいえ、おそらくない」です。