辞書にキーを追加する最速の方法は d[key]=value【Python】

Pythonの辞書(dict)を高速化する方法です。

Pythonの組み込み辞書にキーを追加する方法を調べたら、7種類ありました。

キーの追加にかかる時間を計ったところ、d[key]=value のかたちが一番速かったです。

なので、キーをひとつずつ追加するなら、d[key]=value を使うのが最良という結論になりました。

辞書にキーを追加する7種類の方法

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 の識別子でなければなりません。それ以外の方法では、辞書のキーとして有効などんなキーでも使えます。

組み込み型 マッピング型 — dict class dict(**kwarg)

キーを1つずつ追加するなら d[key]=value がおすすめ!

微々たる差ですが、データ数の多い処理では、少しだけレスポンスが良くなります。

タイトルとURLをコピーしました