検索
連載

[Python入門]辞書Python入門(4/4 ページ)

Pythonには「キーと値」の組を管理するためのデータ構造として「辞書」がある。その基本的な使い方とリストやタプルとの違いなどについて触れる。

Share
Tweet
LINE
Hatena
前のページへ |       

辞書の項目の反復処理とkeys/values/itemsメソッド

 リストやタプルと同様に、for文と辞書を組み合わせることも可能だ。以下に例を示す。

sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
for item in sk:
    print(item)

for文と辞書を組み合わせた例

 この例ではfor文に反復可能オブジェクトとして辞書をそのまま置いている。このコードを実行すると次のようになる。

for文に反復可能オブジェクトとして辞書を渡すとループ変数にはキーが渡される
for文に反復可能オブジェクトとして辞書を渡すとループ変数にはキーが渡される

 ご覧の通り、この場合には辞書のキーがループ変数に渡される。先ほども述べたように、反復可能オブジェクトとして辞書そのものを使う場合には、そのキーが対象となっている。キーに対応する値を得るには次のようなコードを書けばよい(実行結果は省略)。

sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
for item in sk:
    print(item, sk[item])

ループ変数に与えられたキーを使って、その値を得る

 このようにすれば辞書のキーにもその値にもアクセスできるが、辞書には「ビューオブジェクト」を返送する3つのメソッドが備わっている。これらを使えば、辞書のキー/値/キーと値の両者を手軽に扱える。

  • keysメソッド:辞書に格納されているキーを一覧するビューオブジェクトを返す
  • valuesメソッド:辞書に格納されている値を一覧するビューオブジェクトを返す
  • itemsメソッド:辞書に格納されているキーと値の組を一覧するビューオブジェクトを返す

 これらのうちのitemsメソッドを使って先ほどのコードを書き替えると次のようになる。

sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
for key, value in sk.items():
    print(key, value)

itemsメソッドを使って、キーと値の組をタプルとしてループ変数に受け取る

 このコードではitemsメソッドによりキーと値の組をタプルにまとめて反復してくれるオブジェクトを得て、それを使って繰り返し処理を行っている。そのため、キーと値にダイレクトにアクセスできている。

itemsメソッドを使うと、キーと値がタプルにまとめられる
itemsメソッドを使うと、キーと値がタプルにまとめられる

 ところで、「ビューオブジェクト」は辞書に格納されているキーや値、あるいはそれらの組を一覧できる動的なビューを提供するもので、今見たように反復可能オブジェクトのように扱える。「動的」というのは、ビューを作成した後に辞書に対して行われた変更がキーに反映されることを意味している(「動的」の反対語は「静的」であり、仮にビューオブジェクトが静的だとしたら、keysメソッドでキーの一覧を取得した後に辞書に新たに項目を追加しても、それが一覧には反映されない)。

 ビューオブジェクトが動的であることを確認するコードを以下に示す。

mydict = {'foo': 'foo'}
myview = mydict.keys()
print(myview)
mydict['bar'] = 'bar'
print(myview)

ビューオブジェクトを作成した後に、辞書に変更を加える

 ここでは、変数mydictに辞書を代入した後に、keysメソッドを用いて変数myviewにそのビューを取得している。その後、辞書に新たに項目を追加して、その内容を確認している。このコードを実行すると次のようになる。

ビューオブジェクトを作成した後に、辞書に変更を加えても、それが反映される
ビューオブジェクトを作成した後に、辞書に変更を加えても、それが反映される

 最後の出力を見ると、ビューオブジェクトを得た後に辞書に追加した項目のキーも、ビューオブジェクトに含まれているのが分かるはずだ。

 keysメソッドはキーを一覧するビューオブジェクトを返すものだが、for文で反復可能オブジェクトとして辞書自体を渡す場合や、in演算子/not in演算子でキーの存在確認をする場合、辞書に格納されているキーが対象となるのは既に述べた通りなので、keysメソッドにはそれほどの使い道はないかもしれない。

 これに対して、辞書に特定の値が格納されているかを確認したいときにはvaluesメソッドが役に立つだろう。valuesメソッドの使用例を以下に示す(実行結果は省略)。

sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
print('kawasaki' in sk)  # False
print('kawasaki' in sk.values())  # True

valuesメソッドで値の一覧を取得して、そこに文字列'kawasaki'があるかを調べる

 既に見た通り、itemsメソッドはキーと値の組を一覧するビューオブジェクトを提供するものだ(実行結果は省略)。

sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
for key, value in sk.items():
    print(key, value)

itemsメソッドを使って、キーと値の組をタプルとしてループ変数に受け取る(再掲)

 繰り返し処理でキーと値の両者が必要になるときには、このメソッドが便利に使えるだろう。

辞書の使いどころ

 前回の「タプルとリストの違い」では、それらの主な用途として次のようなことを挙げた。

  • リストは「同種のデータを複数個並べて管理する」ためのもの
  • タプルは「異なる種類だが(あるいは同じ種類で)、関連性のあるデータをひとまとめにする」ためのもの

 これに対して、辞書は「格納されているデータに名前を付けることで扱いを容易にする」「整数インデックスでは管理が難しいデータを扱う」ためのものと考えられる。

 例えば、前回は名前と名字と体重を管理するためにタプルを使って、次のようなコードを書いていた。

sk_tuple = ('shinji', 'kawasaki', 80)

名前と名字と体重をタプルにまとめた

 リストでも同様なデータ構造は作成できる。

sk_list = ['shinji', 'kawasaki', 80]

名前と名字と体重をリストにまとめた

 ただし、先ほどの主な用途に照らすと、リストはどちらかといえば「同種のデータを並べて管理する」ものなので、あまりこのやり方はなじまない(もちろん、そうすることは自由だ)。タプルは異種データ(同種データでもよい)をひとまとめにして扱うのに便利なので、上のような使い方をするのは普通のことといえる。

 ただし、タプルでは「何番目の要素が何を表すデータ」なのかが不明だ。インデックス0にあるのが名前で、インデックス1にあるのが名字というのはコードを見れば、想像はできるが、それが正しいかは誰も保証してくれない。これに対して、辞書は値に名前(キー)を付加することで、それが何を意味するものなのかを明確にしてくれる。

sk_dict = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}

名前と名字と体重を辞書にまとめた

 3つのデータ構造にはこのような違いがある。

 また、例えば特定のURLに関するデータ(例えば、筆者、タイトル、ページビューなど)を管理するといった場合にも辞書は便利に使えるだろう。以下はそうしたデータの例だ。

page_data = {
    'https://www.atmarkit.co.jp/ait/articles/1906/14/news015.html':
    {
        'author': 'かわさき しんじ',
        'title': 'タプル',
        'pv': 123456
    },
    'https://www.atmarkit.co.jp/ait/articles/1906/13/news021.html':
    {
        'author': '一色 政彦',
        'title': 'Deep Learningコミュニティー……',
        'pv': 123456789
    },
    'https://www.atmarkit.co.jp/ait/articles/1906/04/news009.html':
    {
        'author': 'かわさき しんじ',
        'title': 'リストの操作',
        'pv': 1234567
    }
}

URLをキーとして、その値(記事名、ページビュー、筆者)を辞書としたデータ

 これはURLをキーとして、そのURLで示されるページの各種データ(記事名、ページビュー、筆者)をさらに辞書にまとめたものをそのキーの値とした辞書だ。このようなデータがあれば、以下のようにページビュー(値となっている辞書から「'pv'」というキーで得られる値)が一番多いURLを調べるといったことも可能になる。

pv = 0
for key, value in page_data.items():
    if value['pv'] > pv:
        top_article = value
        top_article_url = key
        pv = value['pv']

print(f'top article is {top_article["title"]}')
print(f'top author is {top_article["author"]}')
print(f'top page view is {top_article["pv"]}')
print(f'top article url: {top_article_url}')

PVが一番多い記事を調べるコード

 実行結果を以下に示す。

PVが一番多い記事の筆者は「一色」さん
PVが一番多い記事の筆者は「一色」さん

 このように、数値以外の(一意に特定可能な)何かのデータと、それに対応する値を関連付けるといったときに辞書が役に立つ。また、本稿では取り上げないが、Web APIなどを呼び出す際に戻り値としてJSON形式のデータが返されることがよくある。こうしたデータをPythonで扱う際にも辞書が適している(これについては何らかのタイミングで取り上げたい)。

辞書のキーはイミュータブル

 次に辞書のキーに使える値について簡単に説明しておこう。

 Pythonでは、辞書のキーとして使えるものは「変更不可能」(イミュータブル)なオブジェクトだけだ。つまり、文字列(上記コード例のURLなど)やタプルなどは辞書のキーとして使えるが、リストはその要素を変更できるのでキーには使えない。辞書自体も要素の変更や追加、削除が可能なので、他の辞書のキーとして使うことはできない。

 なぜ、リストのようなミュータブルなオブジェクトが辞書のキーに使えないかといえば、一つには辞書のキーにリストを使って項目を登録できたとして、その後で別のキーとして使っている別のリストと同じ要素を持つようにリストを変更してしまったら、そのキーの値が不明になってしまうからだ。

 実行できない架空のコード例を以下に示す。

key_list1 = [0, 1, 2, 3]
key_list2 = [0, 1, 2, 1]
print(key_list1 == key_list2)  # False

mydict = {key_list1: 'foo', key_list2: 'bar'}
key_list1[3] = 1
print(key_list1 == key_list2)  # True

実行できないPythonコード

 この実行できないコード例では、2つのリストkey_list1とkey_list2があり、それらの要素は最初は異なるものとなっている。これら2つのリストをキーとして、辞書を作成したとして(これが実行できない部分)、その後で2つのリストが共に「[0, 1, 2, 1]」という要素を持つようにしたら、「[0, 1, 2, 1]」というキーで得るべき値は'foo'と'bar'のどちらだろう。こうしたことが起こらないように、Pythonでは辞書のキーに使えるのはミュータブルなオブジェクトだけとなっている。

 ただし、「タプルならどんなものでもキーにできる」わけではない。タプルの要素がリストの場合、「タプルの要素であるリストの要素は変更可能」という話を前回にした。そうした場合には、タプルを辞書のキーとすることはできない。

 実際に試してみよう。

key_tuple1 = (1, 2)
key_tuple2 = ([0, 1], [2, 3])  # タプルの要素のリストは変更可能

mydict1 = {key_tuple1: 'foo'}
mydict2 = {key_tuple2: 'bar'# エラー

変更可能な要素を含んだタプルはキーにはできない

 実行した結果を以下に示す。

実行結果
実行結果

 エラーメッセージを読むと「リストはハッシュ不可能(unhashable)な型」とある。実は、Pythonの辞書はキーを基に「ハッシュ値」という値を算出して、関連する値へのアクセスに使用する。そして、Pythonでは、ミュータブルなオブジェクトについては、間違って辞書のキーなどに利用できないように、ハッシュ値を算出する機能を持たないように設計されているので、こうしたエラーが発生したのである。

関数の引数にリストを渡す

 辞書を関数の引数に指定する場合、辞書をそのまま渡す方法と、辞書の要素(キーと値の組)を展開して渡す方法がある。例えば、print関数に辞書を渡すと、辞書の内容がまとめて表示される。

mydict = {'foo': 'FOO', 'bar': 'BAR'}
print(mydict)  # {'foo': 'FOO', 'bar': 'BAR'}

辞書をそのままprint関数に渡す

 辞書の内容を展開して関数に渡すには、2つのアスタリスク「**」を辞書の前に置いたものを引数に指定する。ただし、print関数は任意のキーワード引数を受け取るようにはできていないので、ここでは以下のような関数を定義することにしよう。

def myfunc(mapping=None, **kwargs):
    if mapping:
        print(mapping)
    for key, value in kwargs.items():
        print(key, value)

デフォルト引数値を持つ位置パラメーターとキーワード専用引数を受け取るパラメーターを持つ関数

 これはデフォルト引数値を持つ位置パラメーター「mapping」とキーワード専用引数を受け取るパラメーター「kwargs」を持つ関数の定義だ。辞書をそのまま渡した場合にはパラメーターmappingにそれが渡される、「**」を前置して辞書を渡した場合にはパラメーター「kwargs」に「キー=値」の組として辞書の内容が展開されて渡される。関数の本体では、それらを画面に出力するだけだ。

 この関数に辞書を渡してみるとどうなるかを見てみよう。

mydict = {'foo': 'FOO', 'bar': 'BAR'}
myfunc(mydict)  # 辞書をそのまま渡す
myfunc(**mydict)  # 辞書を展開して渡す

辞書をそのまま渡す場合と、展開して渡す場合

 実行結果を以下に示す。

実行結果
実行結果

 1つ目のmyfunc関数呼び出しでは辞書の内容がそのまま表示され、2つ目の呼び出しではパラメーターkwargsにキーワード引数として渡された値をforループで取り出して、キーと値が表示されていることが分かるはずだ。

 このように辞書の内容を展開して関数に渡すには「**」を前置して引数に指定することを忘れないようにしよう。特に何らかの構成値を辞書として保管しておき、それらをまとめて何かの関数に渡すといったときには、このような呼び出し方をすることになるかもしれない。


まとめ

ここまでのまとめ:辞書

  • 「辞書」とは、「キー」と「値」の組で表される「項目」を複数格納可能なデータ構造のこと
  • 辞書は波かっこ「{}」で囲み、その中に「キー: 値」形式の項目をカンマ区切りで並べていく
  • 1つの辞書には1つのキーに対応する値は1つだけしか格納できない
  • キーには文字列や数値を指定できるが、リストは使えない
  • 値にはどんな値でも指定できる
  • 辞書の値を取り出すには「辞書[キー]」とする
  • 存在しないキーを指定して値を取り出そうとするとエラーになる
  • updateメソッドでも辞書の値を取り出せる
  • updateメソッドでは指定しないキーを指定してもエラーにならない
  • 辞書に項目(「キー: 値」の組)を追加するには、「辞書[新しいキー] = 値」とする
  • updateメソッドを使っても辞書の内容を変更(更新)できる
  • popメソッドで指定したキーの値を取得し、その項目を削除できる
  • popitemメソッドで辞書内の項目を取得し、それを削除できる
  • setdefaultメソッドで、指定したキーが辞書に含まれているかどうかを確認し、あればその値を取得して、なければ新たな項目を追加できる
  • 辞書にはキーを一覧するビューオブジェクトを返すkeysメソッド、値を一覧するビューオブジェクトを返すvaluesメソッド、キーと値の組を一覧するビューオブジェクトを返すitemsメソッドがある
  • 辞書は「格納されているデータに名前を付けることで扱いを容易にする」「整数インデックスでは管理が難しいデータを扱う」ためのもの

「Python入門」のインデックス

Python入門

Copyright© Digital Advantage Corp. All Rights Reserved.

前のページへ |       
[an error occurred while processing this directive]
ページトップに戻る