辞書(dict)#

Jupyter Notebook ファイルの準備

このノートを学習するために、「dict.ipynb」 という名前で Jupyter Notebook のファイルを作成してください。

このノートの練習問題

内容を一通り確認できたら、以下の練習問題に取り組むことでご自身の理解度をチェックしてください。
練習問題:練習問題:辞書 | データ構造

Python の辞書は、Java でいうところの HashMap と同じく、キーと値のペアで構成される要素をもつデータ構造です。また、辞書内の要素をキーを使って参照する点も同じですが、キーにできるデータにはいくつかの制限があったりしますので、このノートを通して詳しく見ていきましょう。

1. 辞書の基本操作#

1-1. 辞書を作成する#

辞書は次のように、 {} 内に要素を キー: の形式で表し、カンマ区切りで並べるリテラルで作成できます。また、要素を指定しなければ、空の辞書を作成できます。

{キー1: 値1, キー2: 値2, キー3: 値3, ...,}
{キー1: 値1, キー2: 値2, キー3: 値3, ...} # 最後のカンマは省略可
{}  # 空の辞書を作成

値はどのようなオブジェクトでも構いませんが、キーはハッシュ可能(※)なオブジェクトに限定されます。そのため、リストや集合、別の辞書をキーに指定することはできません。

Note

※ リストや集合、辞書など要素を後から変更できる、可変な性質をもつオブジェクトはハッシュ不可です。また、タプルはすべての要素がハッシュ可能な場合のみ、タプル自体もハッシュ可能です。

次の例では、キーとしてタプルを指定していますが、これらのタプルはすべての要素がハッシュ可能であるため、辞書のキーとして使用できています。

reversi_board = {('D', 4): "white",
                 ('E', 4): "black",
                 ('D', 5): "white",
                 ('E', 5): "black"}
print(reversi_board)
{('D', 4): 'white', ('E', 4): 'black', ('D', 5): 'white', ('E', 5): 'black'}

1-2. キーを使って値を取得する#

辞書内の要素の検索にはキーを使います。[] を用いたブラケット表記法または辞書がもつメソッドの呼び出しでキーを指定すると、そのキーをもつ要素が見つかれば、値が取得できます。

  • ブラケット表記法:[] 内にキーを指定

  • get(key) メソッド:引数にキーを指定

以下の例では商品番号を表す文字列をキーにして、商品一覧を表す辞書を作成しています。そして、商品番号でその辞書から各商品を検索しています。

catalog = {"10001": ("USB memory 8GB", 1980),
           "10002": ("USB memory 16GB", 2980),
           "10003": ("USB memory 32GB", 3980)}

print(catalog["10001"])  # ('USB memory 8GB', 1980)
print(catalog.get("10003"))  # ('USB memory 32GB', 3980)
('USB memory 8GB', 1980)
('USB memory 32GB', 3980)

get メソッドによる検索では、該当する要素が見つからなかった場合は None が返されます。一方でブラケット表記法での検索は、該当する要素が見つからない場合、 KeyError が送出されるという違いがあります。

catalog = {"10001": ("USB memory 8GB", 1980),
           "10002": ("USB memory 16GB", 2980),
           "10003": ("USB memory 32GB", 3980)}
key = "10004"

# get メソッドを用いた検索
if (product := catalog.get(key)) == None:
    print(f'商品番号{key}に該当する商品が見つかりません。')
else:
    print(product)

# ブラケット表記法を用いた検索
try:
    product = catalog[key]
except KeyError:
    print(f'商品番号{key}に該当する商品が見つかりません。')
else:
    print(product)
商品番号10004に該当する商品が見つかりません。
商品番号10004に該当する商品が見つかりません。

1-3. 要素を追加する#

要素を追加する方法には、以下の 3 つがあります。それぞれの特徴を理解して、使い分けられるようになりましょう。

  • ブラケット表記法と代入演算子の組み合わせ

  • update() メソッド

  • setdefault() メソッド

ブラケット表記法と代入演算子の組み合わせ#

次のように、代入演算子の左辺にブラケット表記法でキーを指定し、右辺に値を指定すると、このキーと値のペアで辞書に新たな要素が追加できます。

辞書を代入した変数[キー] = 

ただし、既に同じキーをもつ要素が辞書にある場合は、追加ではなく、値の更新が行われます

di = {"key1": "value1"}
di["key2"] = "value2"  # 新しいキーと値のペアが追加される
di["key1"] = "updated_value1"  # 既に同一のキーが存在している場合ため値が更新される
print(di)  # {'key1': 'updated_value1', 'key2': 'value2'}
{'key1': 'updated_value1', 'key2': 'value2'}

update() メソッド#

update() メソッドは引数に指定した辞書がもつすべての要素を、呼び出し元の辞書に追加します。ブラケット表記法を用いた場合と違い、複数の要素をまとめて追加できるのが、この方法の大きなメリットです。

di = {"key1": "value1"}
di.update({"key1": "updated_value1",  # 引数の辞書が持つ要素がすべて追加される
           "key2": "value2"})         # ただし、既存のキーに対しては値の更新になる                      
print(di)  # {'key1': 'updated_value1', 'key2': 'value2'}
{'key1': 'updated_value1', 'key2': 'value2'}

また、引数には辞書以外にも、2次元のタプルやリストを代わりに指定することもできます。この場合、各要素は (キー, 値) または [キー, 値] の形式で表したタプルやリストでなければいけません。

# 2次元のタプルで指定した場合
di = {"key1": "value1"}
# タプルの各要素は、要素数 2 のタプル
di.update((("key1", "updated_value1"), ("key2", "value2")))
print(di)

# 2次元のリストで指定した場合
di = {"key1": "value1"}
# リストの各要素は、要素数 2 のリスト
di.update([["key1", "updated_value1"], ["key2", "value2"]])
print(di)
{'key1': 'updated_value1', 'key2': 'value2'}
{'key1': 'updated_value1', 'key2': 'value2'}

他には、後のノートで説明する関数のデフォルト引数を利用して、追加する要素を指定できます。引数名がキー、実引数が値として辞書に格納されます。

di = {"key1": "value1"}
# 「キー = 値」で引数を指定(デフォルト引数)
di.update(key1="updated_value1", key2="value2")
print(di)  # {'key1': 'updated_value1', 'key2': 'value2'}
{'key1': 'updated_value1', 'key2': 'value2'}

setdefault() メソッド#

setdefault(key, value) メソッドは指定したキーが存在しない場合と、存在する場合で、次のように振る舞いが変わります。

  • 指定したキーが存在しない場合:新しいキーと値のペアを追加する。また、追加した値を戻り値として返す。

  • 指定したキーが存在する場合:値は更新しない。また、現在の値を戻り値として返す。

di = {"key1": "value1"}

# キーが存在しない場合は新しいキーと値のペアを追加する
# 新しく追加した値が戻り値として返される
ret_val = di.setdefault("key2", "value2")
print(ret_val, di) # value2 {'key1': 'value1', 'key2': 'value2'}

# 同一のキーが存在している場合は値を更新しない
# 既存の値が戻り値として返される
ret_val = di.setdefault("key1", "updated_value1")
print(ret_val, di)  # value1 {'key1': 'value1', 'key2': 'value2'}
value2 {'key1': 'value1', 'key2': 'value2'}
value1 {'key1': 'value1', 'key2': 'value2'}

1-4. キーを使って要素を削除する#

辞書は、del 命令もしくは pop メソッドでキーを指定して要素を削除できます。

  • del 命令とブラケット表記法の組み合わせ:指定されたキーを持つ要素を削除する

  • pop(key):指定されたキーをもつ要素を削除してその値を返す

del 命令は要素を削除するだけですが、pop メソッドは要素を削除した上でその値を戻り値として返します。どちらの方法も、該当する要素が見つからない場合は KeyError が送出されます。

catalog = {"10001": ("USB memory 8GB", 1980),
           "10002": ("USB memory 16GB", 2980),
           "10003": ("USB memory 32GB", 3980)}

# del 命令で削除
del catalog["10001"]  # 削除した要素の値は返さない

# pop(key) メソッドで削除
deleted_value = catalog.pop("10002")  # 削除した要素の値を返す

print(catalog)  # {'10003': ('USB memory 32GB', 3980)}
print(deleted_value)  # ('USB memory 16GB', 2980)
{'10003': ('USB memory 32GB', 3980)}
('USB memory 16GB', 2980)

1-5. 最後の要素を削除して取得する#

辞書は集合と同じく、基本は要素間に順番がないデータ構造です。ただし、Python のバージョン 3.7 以降からは、登録順序が保持されるようになりました。 popitem() メソッドを使うと、最後に登録された要素を削除し、その要素を (キー, 値) のタプルで取得することができます。

fruits = {}  # キーがフルーツ名で値が価格になっている要素をもつ辞書
fruits.update({'banana':250})
fruits.update({'apple':150})
fruits.update({'strawberry':500})
fruits.update({'orange':180})

# popitem メソッドは上の update メソッドで最後に登録された要素から順番に削除する
# 削除した要素は (キー, 値) のタプルとして返される
print(fruits.popitem())  # ('orange', 180)
print(fruits.popitem())  # ('strawberry', 500)
print(fruits.popitem())  # ('apple', 150)
print(fruits.popitem())  # ('banana', 250)
print(fruits)            # {} ← 空の辞書
('orange', 180)
('strawberry', 500)
('apple', 150)
('banana', 250)
{}

1-6. すべての要素を削除する#

辞書からすべての要素を削除したい場合は clear メソッドを使います。

catalog = {"10001": ("USB memory 8GB", 1980),
           "10002": ("USB memory 16GB", 2980),
           "10003": ("USB memory 32GB", 3980)}
catalog.clear() # すべての要素が削除される
print(catalog)  # {} : 空の辞書
{}

1-7. 浅いコピーを作成する#

辞書は copy メソッドで複製できます。 ただし、これは浅いコピー(shallo copy) であるため、コピー元の辞書とコピー先の辞書がもつ各要素は、どちらも同じオブジェクトを参照しています。

di = {'nums': [1, 2, 3], 'letters': ['a', 'b', 'c'], 'bools': (True, False)}

# copy メソッドは浅いコピーを作成
di_copied = di.copy()
print(di_copied)
# コピー元とコピー先の 2 つは異なるオブジェクト
print(di is di_copied)  # False
# ただし浅いコピーであるため、各要素が参照しているオブジェクトは同じ
print(di['nums'] is di_copied['nums'])        # True
print(di['letters'] is di_copied['letters'])  # True
print(di['bools'] is di_copied['bools'])      # True
{'nums': [1, 2, 3], 'letters': ['a', 'b', 'c'], 'bools': (True, False)}
False
True
True
True

1-8. 辞書から繰り返し要素を取得する#

辞書から繰り返し要素を取得したい場合は for 文を使います。このとき for の後に指定した変数には、各要素のキーが代入されます。そのため、値を取得したい場合は、ループ内でブラケット表記法や get() メソッドを用います。

catalog = {"10001": ("USB memory 8GB", 1980),
           "10002": ("USB memory 16GB", 2980),
           "10003": ("USB memory 32GB", 3980)}

for key in catalog:  # 変数に代入されるのはキー
    # 値はブラケット表記法や get メソッドでキーを指定して取得
    print(f'商品番号:{key}, 商品詳細:{catalog[key]}')
商品番号:10001, 商品詳細:('USB memory 8GB', 1980)
商品番号:10002, 商品詳細:('USB memory 16GB', 2980)
商品番号:10003, 商品詳細:('USB memory 32GB', 3980)

1-9. キーが存在しているかどうかを調べる#

辞書に、ある値と等しい値のキーが存在しているかどうかを調べたい場合は、 in 演算子や not in 演算子を使います。

  • key in dictkey と等しい値のキーが dict にあれば True を返し、なければ False を返します。

  • key not in dictin 演算子と反対の結果を返します。

catalog = {"10001": ("USB memory 8GB", 1980),
           "10002": ("USB memory 16GB", 2980),
           "10003": ("USB memory 32GB", 3980)}
print("10001" in catalog)  # True
print("10004" in catalog)  # False
print("10001" not in catalog)  # False
print("10004" not in catalog)  # True

# in 演算子や not in 演算子は要素の値が比較の対象ではないので注意
print("---")
print(("USB memory 8GB", 1980) in catalog)  # False
True
False
False
True
---
False

このように、比較の対象は要素のキーであることに注意してください。もし、キーではなく、値の方を対象として存在の有無を調べたい場合は、後述の values() メソッドを利用します。

2. 少し高度な辞書の操作#

2-1. 値のみやキーと値のペアを繰り返し取得する#

前のセクションでは、 for 文を用いて、辞書から繰り返しキーを取得できることを説明しました。実は、辞書がもつ以下のメソッドを利用することで、キー以外にも値や、キーと値のペアを繰り返し取得することができます。

  • keys() メソッド:辞書がもつキーの一覧を表すオブジェクトを返します。

  • values() メソッド:辞書がもつ値の一覧を表すオブジェクトを返します。

  • items() メソッド:辞書がもつ要素をそれぞれ(キー, 値) のタプルとして表したオブジェクトを返します。

いずれのメソッドも、戻り値は繰り返し可能オブジェクトです。そのため、for 文の in キーワードの後に指定することで、順番に要素が取得できます。

  • keys メソッドのコード例

# keys() メソッドはキーの一覧を取得
fruits = {"apple": 100, "orange":120}
for key in fruits.keys():
    print(key)
apple
orange
  • values メソッドのコード例

# values() メソッドは値の一覧を取得
fruits = {"apple": 100, "orange":120}
for value in fruits.values():
    print(value)
100
120
  • items メソッドのコード例

# items() メソッドは要素を (キー,値) のタプルで表した一覧を取得
fruits = {"apple": 100, "orange":120}
for item in fruits.items():
    print(item)  # (キー, 値) のタプル
('apple', 100)
('orange', 120)

items メソッドを場合、タプルのアンパックを利用すると、キーと値をそれぞれ別の変数に代入して取得することができます。

# アンパックを用いた例
fruits = {"apple": 100, "orange":120}
# for の後に代入先の変数をカンマ区切りで並べて書く
for key, value in fruits.items():
    print(key, value)
# 上は以下のコードと同じ処理
# for item in fruits.items():
#     key, value = item
#     print(key, value)
apple 100
orange 120

2-2. 辞書のアンパック#

リストやタプルと同じく、辞書もアンパックができます。ただし、アスタリスクを先頭にひとつ付けた場合は、アンパックの対象はキーだけになります。

di = {"apple": 100, "orange":120, "banana": 200, "strawberry": 500}

# 変数名の先頭につけて、キーの一覧をリスト化する例
keys = [*di]
print(keys)

# キーを複数の変数に分割代入する例
start_key, *rest_keys, end_key = di
print(start_key, rest_keys, end_key)  # apple ['orange', 'banana'] strawberry
['apple', 'orange', 'banana', 'strawberry']
apple ['orange', 'banana'] strawberry

キーと値のペアをアンパックの対象にしたい場合は、先頭にアスタリスクをふたつ付けます。この場合、キー = の形式で展開されるため、後のノートで説明する、関数のデフォルト引数の指定に辞書の要素をそのまま使うことができます。

di_1 = {'key1': 'value1', 'key2': 'value2'}
di_2 = {'key3': 'value3', 'key4': 'value4'}

di_1.update(**di_2)
# 上のコードは以下のように記述した場合と同じ
# di_1.update(key3='value3', key4='value4')

print(di_1)
{'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'key4': 'value4'}

おわりに#

ここまでに、リスト、タプル、集合、辞書と内部に複数の要素をもつ 4 種類のデータ構造を見てきました。以下の表で改めてそれぞれの特徴を整理し、適切に使い分けれらるようになりましょう。

データ構造

要素を参照する方法

要素の追加・削除・更新

要素間の順番

要素の重複

リスト

インデックスで参照

できる

順番がある

できる

タプル(tuple)

インデックスで参照

できない

順番がある

できる

集合(set)

インデックスやキーでは参照できない

できる

順番はない

できない

辞書(dict)

キーで参照

できる

基本は順番はないが登録順序は保持される

(キーの重複は)できない