Pythonの辞書(dict)を高速化する方法です。
Pythonの組み込み辞書にキーを追加する方法を調べたら、7種類ありました。
キーの追加にかかる時間を計ったところ、d[key]=value のかたちが一番速かったです。
なので、キーをひとつずつ追加するなら、d[key]=value を使うのが最良という結論になりました。
辞書にキーを追加する7種類の方法
Pythonマニュアルにいろいろなパターン紹介されていました。
(Python) 組み込み型 マッピング型 — dict class dict(**kwarg)
7種類と書きましたが、タプルとリストの組み合わせを変えれば、もう少しバリエーションが作れると思います。
シンプル
d[key]=value でキーを追加
タプル(tuple)を渡す方法
d.update(((key1, value1), …)) でキーを追加
リスト(list)を渡す方法
d.update([(key1, value1), …]) でキーを追加
辞書(dict)を渡す方法
d.update({key1: value1, …}) でキーを追加
zip()でタプルを束ねて渡す方法
d.update(zip((key1, …), (value1, …))) でキーを追加
zip()でリストを束ねて渡す方法
d.update(zip([key1, …], [value1, …])) でキーを追加
キーワード引数を使う方法 ※引数名に制約あり
d.update(key1=value1, …) でキーを追加
辞書にキーを追加する速さの比較
6種類のキー追加方法で実験しました。
最速は d[key]=value の方法で、次に速かったのが辞書を渡す方法 d.update({key1: value1, …}) でした。
※ キーワード引数を使う方法は、キーを動的に変えられなかったので、別枠で計測しました。
時間計測コード
タイムイット(timeit)で時間を計りました。
関数を6種類作ってから、ひとつずつタイムイットの中で実行します。
パイソンは Python 3.6.6 – 64bit を使いました。ほかのコードも同様です。
"""辞書に要素を追加する速さの比較"""
import timeit
# 1/6 シンプル
# d[key]=value でキーを追加
def dict_key_a(n_max):
d = {}
for n in range(n_max):
d[n] = 0
return
# 2/6 タプル(tuple)を渡す方法
# d.update(((key1, value1), ...)) でキーを追加
def dict_key_b(n_max):
d = {}
for n in range(n_max):
d.update(((n, 0),))
return
# 3/6 リスト(list)を渡す方法
# d.update([(key1, value1), ...]) でキーを追加
def dict_key_c(n_max):
d = {}
for n in range(n_max):
d.update([(n, 0)])
return
# 4/6 辞書(dict)を渡す方法
# d.update({key1: value1, ...}) でキーを追加
def dict_key_d(n_max):
d = {}
for n in range(n_max):
d.update({n: 0})
return
# 5/6 zip()でタプルを束ねて渡す方法
# d.update(zip((key1, ...), (value1, ...))) でキーを追加
def dict_key_e(n_max):
d = {}
for n in range(n_max):
d.update(zip((n, ), (0, )))
return
# 6/6 zip()でリストを束ねて渡す方法
# d.update(zip([key1, ...], [value1, ...])) でキーを追加
def dict_key_f(n_max):
d = {}
for n in range(n_max):
d.update(zip([n], [0]))
return
# 繰り返し回数 1回
NUMBER = 1
# 関数内のfor文の繰り返し回数 100万回
N_MAX = 1000000
# グローバル名前空間(グローバルシンボルテーブル)の辞書を取得
g = globals()
ta = timeit.timeit('dict_key_a(N_MAX)', globals=g, number=NUMBER)
print('%f seconds d[key]=value\n' % ta)
tb = timeit.timeit('dict_key_b(N_MAX)', globals=g, number=NUMBER)
print('%f seconds d.update(((key1, value1), ...))\n' % tb)
tc = timeit.timeit('dict_key_c(N_MAX)', globals=g, number=NUMBER)
print('%f seconds d.update([(key1, value1), ...])\n' % tc)
td = timeit.timeit('dict_key_d(N_MAX)', globals=g, number=NUMBER)
print('%f seconds d.update({key1: value1, ...})\n' % td)
te = timeit.timeit('dict_key_e(N_MAX)', globals=g, number=NUMBER)
print('%f seconds d.update(zip((key1, ...), (value1, ...)))\n' % te)
tf = timeit.timeit('dict_key_f(N_MAX)', globals=g, number=NUMBER)
print('%f seconds d.update(zip([key1, ...], [value1, ...]))\n' % tf)
実行結果
最速が d[key]=value で、その次が d.update({key1: value1, …}) でした。
0.118120 seconds d[key]=value
0.524927 seconds d.update(((key1, value1), ...))
0.529265 seconds d.update([(key1, value1), ...])
0.274029 seconds d.update({key1: value1, ...})
0.699167 seconds d.update(zip((key1, ...), (value1, ...)))
0.759598 seconds d.update(zip([key1, ...], [value1, ...]))
タプルやリストの生成に時間がかかっている
おそらくですが、ほかの方法はリストやタプルを2回以上生成しているので、そのぶん時間がかかったんだと思います。
また、リストの生成はタプルよりも遅かったです(微々たる差ですが)。
『辞書の生成』はタプルやリストよりも遅いですが、1回の生成で済んでいたので、2番目に速かったんだと思います。
tuple、list、dictの生成速度比較
タイムイット(timeit)で時間を計りました。
空のタプル、空のリスト、空の辞書の生成速度を比較しました。
"""空のタプル、空のリスト、空の辞書の生成速度比較"""
import timeit
# 繰り返し回数 100万回
NUMBER = 1000000
ta = timeit.timeit('()', number=NUMBER)
print('%f seconds () tuple\n' % ta)
tb = timeit.timeit('[]', number=NUMBER)
print('%f seconds [] list\n' % tb)
tc = timeit.timeit('{}', number=NUMBER)
print('%f seconds {} dict\n' % tc)
実行結果です。一番シンプルなタプルが一番高速でした。
0.014899 seconds () tuple
0.023193 seconds [] list
0.043965 seconds {} dict
.update()にキーワード引数を使う方法の速さ
残りの1種類は アップデートメソッド .update() にキーワード引数を使う方法です。
こちらはキーを引数名に設定する関係で、数値 1
や数値の文字列 '1'
をキーにすることができません。
また、引数名を動的に変えられないので、別のキーを追加していくこともできません。
一応、値を上書きする方法で比較してみましたが、d[key]=value のほうが高速だったので、.update() にキーワード引数を使う方法はあまり用途がないかもしれません。
アップデートメソッドの時間計測コード
タイムイット(timeit)で時間を計りました。
"""辞書に値を上書きする速さの比較"""
import timeit
# シンプル
# d[key]=value でキーを上書き
def dict_key_a(n_max):
d = {}
for n in range(n_max):
# d['a']に n を上書き
d['a'] = n
# キーワード引数を使う方法
# d.update(key1=value1, ...) でキーを上書き
def dict_key_z(n_max):
d = {}
for n in range(n_max):
# d['a']に n を上書き
d.update(a=n)
# 繰り返し回数 1回
NUMBER = 1
# 関数内のfor文の繰り返し回数 100万回
N_MAX = 1000000
# グローバル名前空間(グローバルシンボルテーブル)の辞書を取得
g = globals()
ta = timeit.timeit('dict_key_a(N_MAX)', globals=g, number=NUMBER)
print('%f seconds d[key]=value\n' % ta)
tz = timeit.timeit('dict_key_z(N_MAX)', globals=g, number=NUMBER)
print('%f seconds d.update(key1=value1, ...)\n' % tz)
キーワード引数を使った実行結果
d[key]=value のほうが速かったです。
0.047833 seconds d[key]=value
0.160201 seconds d.update(key1=value1, ...)
キーワード引数を使う方法には制約がある
アップデートメソッド .update()にキーワード引数を使う方法では、数字をキーにすることができません。
d = {}
# NG1
d.update(1=0)
SyntaxError: keyword can't be an expression
# NG2
d.update('1'=0)
SyntaxError: keyword can't be an expression
このあたりの内容は、Pythonの公式マニュアルにも書かれていました。
キーワード引数を与える方法では、キーは有効な Python の識別子でなければなりません。それ以外の方法では、辞書のキーとして有効などんなキーでも使えます。
キーを1つずつ追加するなら d[key]=value
微々たる差ですが、データ数の多い処理では、少しだけレスポンスが良くなりました。
以上です。