突然ですが次のコードは例外をスローします。なぜでしょうか?
import collections
ht = collections.defaultdict(int)
ht[0] = ht[1] = 1
x = 2
for ky in ht:
if ht[ky + 1] == 0: do_something()
#=> RuntimeError: dictionary changed size during iteration
Python3ではループ中に対象の辞書のサイズが変化するとRuntimeError
を投げます。collections.defaultdict
も同じで、上記の例ではky = 1
のときht[2]
という未定義領域にアクセスした結果、ht[2] = 0
と定義されてしまいます。そのためループ中に辞書のサイズが変化したとして例外になるのです。
collections.defaultdict
は未定義領域に問答無用でアクセスできるという点でとても便利ですが、だからといって__「とあるkeyのvalueがすでに定義されているかどうか」のチェックに際して、「とりあえずそのkeyでアクセスしてみて、帰ってきた値がデフォルト値だったら結果的に未定義だったとわかる」という方法は推奨できません___。上記の例がその典型といえるでしょう。ややまどろっこしくともif ky in ht
のように、きちんと存在チェックを行いましょう。
ちなみにRubyのHash
の場合はどうでしょう? 実は次の例ではエラーにはなりません。
hash = Hash.new(0)
hash[0] = hash[1] = 1
hash.each{|ky, val| p hash[ky + 100] if hash[ky + 100] == 0}
#=> 0 0
Rubyの場合Hash
の未定義領域にアクセスすると、規定されたデフォルト値を返しますが、___明示的な代入行為が行われない限りは定義されたとみなされず、したがってHash
のサイズは変動しません。___未定義領域にアクセスした瞬間にkeyに対するvalueをデフォルト値で定義してしまうPython3のcollections.defaultdict
とは性質がかなり異なります。
なおループ中にHash
のサイズが変動するとエラーになるのはRubyも同じです。残念(´・ω・`)
hash = Hash.new(0)
hash[0] = hash[1] = 1
hash.each{|ky, val| hash[ky + 100] += 1 if hash[ky + 100] == 0}
#=> can't add a new key into hash during iteration (RuntimeError)