LoginSignup
1
0

More than 5 years have passed since last update.

ループ中にdefaultdictの未定義領域にアクセスすると例外

Last updated at Posted at 2016-12-12

突然ですが次のコードは例外をスローします。なぜでしょうか?

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)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0