【Python】辞書(dict)の使い方|追加・取得・ループ・get・setdefault

【Python】辞書(dict)の使い方|追加・取得・ループ・get・setdefault Python

辞書(dict)は、キーと値のペアでデータを持つ、Pythonの基本的なデータ構造です。リストが「順番(位置)」で値を取り出すのに対し、辞書は「名前(キー)」で値を取り出します{"name": "山田", "age": 30}のように波かっこで書きます。

もっともつまずきやすいのは、存在しないキーを[]で取り出そうとするとKeyErrorでプログラムが止まることです。これを避けるgetの使い方が、辞書を安全に扱うカギになります。この記事では、実機のPythonで確認しながら、辞書の使い方を整理します。

先に結論

  • 作成は{"name": "山田", "age": 30}、空は{}です。
  • 取り出しはd["name"]存在しないキーはKeyErrorになります。
  • 安全に取り出すならd.get("key")(無ければNone)、既定値はd.get("key", 既定)です。
  • 追加・更新はどちらもd["key"] = 値。まとめて足すならd.update({...})です。
  • ループはfor key, value in d.items():。辞書は追加した順番を保ちます(Python 3.7以降)。
  • "key" in dキーの存在を調べます(値ではありません)。

もう1つの基本データ構造であるリストはリスト(list)の使い方、辞書のループに使う繰り返しはforループで繰り返し処理を行う方法、連番作成はrange関数の使い方もあわせて参考になります。

スポンサーリンク

辞書を作る

辞書は波かっこ{ }でキーと値を:でつないで書きます。dict()関数でも作れます。

create.py
# キー: 値 のペアで作る
user = {"name": "山田", "age": 30, "city": "東京"}

# 空の辞書
config = {}

# dict() でも作れる
user2 = dict(name="佐藤", age=25)

# ペアのリストから作る
pairs = dict([("a", 1), ("b", 2)])   # {'a': 1, 'b': 2}

# キーは文字列以外(数値など)も使える
scores = {1: "金", 2: "銀", 3: "銅"}

キーには文字列だけでなく、数値なども使えます。値には何でも入れられます(リストや別の辞書も可能です)。

値を取り出す([] と get)

値はキーを指定して取り出します。方法は2つあり、d["key"]d.get("key")です。違いはキーが存在しないときの挙動です。

access.py
user = {"name": "山田", "age": 30}

# [] で取り出す
user["name"]        # 山田
user["age"]         # 30

# get で取り出す(存在しなくてもエラーにならない)
user.get("name")    # 山田
user.get("xxx")     # None(存在しないキー)
user.get("xxx", "不明")   # 不明(既定値を指定できる)

【重要】存在しないキーはKeyError

ここが最大の注意点です。d["存在しないキー"]と書くと、KeyErrorという例外が発生してプログラムが止まります。一方、d.get("存在しないキー")Noneを返すだけで止まりません。

keyerror.py
user = {"name": "山田"}

# NG: 存在しないキーを [] で取り出すと KeyError で止まる
# print(user["age"])   # KeyError: 'age'

# OK: get なら安全(無ければ None)
print(user.get("age"))        # None
print(user.get("age", 0))     # 0(既定値を返す)

# キーがあるか確認してから [] で取り出す方法もある
if "age" in user:
    print(user["age"])
外部データはgetで安全に取り出す

実機で確認したところ、辞書に無いキーをuser["xxx"]で取り出すとKeyErrorになり、user.get("xxx")Noneuser.get("xxx", "不明")"不明"を返しました。JSONやフォームから受け取ったデータなど、キーがあるか分からないときgetを使うのが安全です。確実にキーがある場合や、無いことがバグだと知らせたい場合は、あえて[]を使う、と使い分けます。

追加・更新する

値の追加と更新は、どちらもd["key"] = 値で行います。キーが無ければ追加、あれば更新です。複数をまとめて反映するならupdateを使います。

add-update.py
d = {"a": 1}

# 追加(キーが無いとき)
d["b"] = 2          # {'a': 1, 'b': 2}

# 更新(キーがあるとき)
d["a"] = 99         # {'a': 99, 'b': 2}

# まとめて追加・更新
d.update({"c": 3, "a": 100})   # {'a': 100, 'b': 2, 'c': 3}

# 2つの辞書を結合する(Python 3.9 以降)
merged = {"x": 1} | {"y": 2}   # {'x': 1, 'y': 2}

実機でも、d["a"] = 99で既存キーが更新され、updateでまとめて反映できました。追加と更新が同じ書き方なので、「無ければ作る、あれば上書き」を意識せずに書けます。

削除する(del / pop)

削除はdelまたはpopを使います。popは削除した値を返し、既定値を指定すれば存在しないキーでも安全です。

remove.py
d = {"a": 1, "b": 2, "c": 3}

# キーで削除
del d["a"]          # {'b': 2, 'c': 3}

# 削除して値を受け取る
value = d.pop("b")  # value=2, d={'c': 3}

# 存在しないキーは del も pop もエラーになる
# del d["zzz"]      # KeyError
# pop は既定値を指定すれば安全
safe = d.pop("zzz", None)   # None(エラーにならない)

# すべて削除
d.clear()           # {}

実機でも、del d["a"]でキーを削除でき、d.pop("b")は削除した値2を返しました。popに既定値を渡すと、存在しないキーでもエラーにならず既定値が返ります。delと既定値なしのpopは、存在しないキーでKeyErrorになる点に注意してください。

ループ処理する(keys / values / items)

辞書をループするには3つの方法があります。キーだけ、値だけ、キーと値の両方です。実務でいちばん使うのはitems()です。

loop.py
user = {"name": "山田", "age": 30, "city": "東京"}

# キーでループ(そのまま for で回すとキー)
for key in user:
    print(key)          # name, age, city

# 値でループ
for value in user.values():
    print(value)        # 山田, 30, 東京

# キーと値の両方(最もよく使う)
for key, value in user.items():
    print(key, value)   # name 山田 / age 30 / city 東京

実機でも、辞書をそのままforで回すとキーが取れ、items()でキーと値を同時に取り出せました。Python 3.7以降、辞書は追加した順番を保つため、ループでも書いた順に出てきます。古い情報では「辞書は順序を保証しない」とされていますが、現在のPythonでは順序が保たれます。

キーの存在確認(in はキーを見る)

あるキーが辞書にあるかはinで調べます。注意点は、inが調べるのは「キー」であって「値」ではないことです。

in-check.py
user = {"name": "山田", "age": 30}

"name" in user        # True(キーがある)
"xxx" in user         # False
"name" not in user    # False

# 注意: in はキューを見る。値では False になる
"山田" in user        # False("山田" は値であってキーではない)

# 値で探したいときは values() を見る
"山田" in user.values()   # True

len(user)             # 2(ペアの数)

実機でも、"name" in userTrue、値である"山田"in userで調べるとFalseになりました。値が含まれるか調べたいときはin user.values()を使います。「キーで判定しているのか、値で判定しているのか」を意識してください。

出現回数を数える(get / setdefault)

辞書がもっとも活きるのが集計です。「キーごとの回数」を数えるときはget、「キーごとにリストへ振り分ける」ときはsetdefaultが便利です。

count.py
# 出現回数を数える(get で「無ければ0」から +1)
words = ["a", "b", "a", "c", "a", "b"]
counts = {}
for w in words:
    counts[w] = counts.get(w, 0) + 1
print(counts)   # {'a': 3, 'b': 2, 'c': 1}

# キーごとにリストへ振り分ける(setdefault で「無ければ空リスト」)
members = [("A", "赤"), ("B", "青"), ("C", "赤")]
groups = {}
for name, team in members:
    groups.setdefault(team, []).append(name)
print(groups)   # {'赤': ['A', 'C'], '青': ['B']}

実機でも、counts.get(w, 0) + 1で出現回数が{'a': 3, 'b': 2, 'c': 1}と数えられ、setdefault(team, []).append(name)でチームごとにメンバーを振り分けられました。setdefault("key", 既定)は「キーがあればその値、無ければ既定値を設定して返す」働きをします。より高度な集計には、標準ライブラリのcollections.Counterdefaultdictも便利です。

キーは変更できない値だけ

辞書のキーには、文字列・数値・タプルなど変更できない(不変な)値しか使えません。リストのような変更できる値はキーにできず、使おうとするとTypeErrorになります。

key-types.py
# OK: 文字列・数値・タプルはキーにできる
d = {"a": 1, 10: "x", (1, 2): "座標"}

# NG: リストはキーにできない(変更できる値だから)
# bad = {[1, 2]: "x"}   # TypeError: unhashable type: 'list'

# 値には何でも入れられる(リストや辞書もOK)
data = {"items": [1, 2, 3], "meta": {"count": 3}}

実機でも、リストをキーにしようとするとTypeError: unhashable type: 'list'になりました。一方、には何でも入れられます(リストや別の辞書も可能です)。「キーは固定の名前、値は自由」と覚えておくとよいでしょう。

よくある失敗

存在しないキーを[]で取り出してKeyError

キーがあるか分からないときはd.get("key")(無ければNone)かd.get("key", 既定値)を使います。

inで値を探そうとする

inはキーを調べます。値が含まれるか調べたいときは値 in d.values()を使います。

辞書は順序を保証しないと思い込む

Python 3.7以降、辞書は追加した順番を保ちます。古い情報に惑わされないようにします。

リストをキーにしようとする

キーには変更できない値(文字列・数値・タプル)しか使えません。リストはキーにできずTypeErrorになります。

カウントでいきなり += する

初めてのキーは存在しないため、counts[w] += 1はKeyErrorになります。counts.get(w, 0) + 1のように「無ければ0」から始めます。

よくある質問

Q[]とgetはどう使い分けますか?
A確実にキーがあるときはd["key"]、キーがあるか分からないときはd.get("key")です。getは存在しないキーでもエラーにならずNone(または指定した既定値)を返すため、外部から受け取ったデータの取り出しに向きます。
Q辞書をキーと値の両方でループするには?
Afor key, value in d.items():を使います。items()がキーと値のペアを返すため、両方を同時に受け取れます。キーだけならfor key in d:、値だけならfor value in d.values():です。
Q辞書に値が含まれるか調べるには?
A値 in d.values()を使います。値 in dはキーを調べるため、値ではFalseになります。キーを調べるのか値を調べるのかで、in din d.values()を使い分けます。
Q辞書の順番は保たれますか?
APython 3.7以降は、追加した順番が保たれます。ループや表示でも書いた順に出てきます。順序を前提にした処理を書いても問題ありません(古いバージョンでは保証されませんでした)。
Qキーごとに回数を数えるには?
Acounts[w] = counts.get(w, 0) + 1が定番です。初めてのキーはget(w, 0)で0から始まるためKeyErrorになりません。より簡単には標準ライブラリのcollections.Counterも使えます。

まとめ

  • 辞書は{"name": "山田"}で作り、d["name"]でキーから値を取り出します。
  • 存在しないキーは[]だとKeyError。安全に取るならd.get("key", 既定)です。
  • 追加・更新はどちらもd["key"] = 値、まとめてならupdateです。
  • ループはfor key, value in d.items():。順番は保たれます(3.7以降)。
  • inキーを調べます。値ならin d.values()です。
  • 集計はgetでカウント、setdefaultでグループ化が定番です。

辞書は、名前で値を出し入れできる便利なデータ構造です。「存在しないキーはgetで安全に」「inはキーを見る」「集計はgetsetdefault」の3点を押さえれば、設定の管理から集計まで、幅広く活用できます。