リストやタプルと同様に、for文と辞書を組み合わせることも可能だ。以下に例を示す。
sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
for item in sk:
print(item)
この例ではfor文に反復可能オブジェクトとして辞書をそのまま置いている。このコードを実行すると次のようになる。
ご覧の通り、この場合には辞書のキーがループ変数に渡される。先ほども述べたように、反復可能オブジェクトとして辞書そのものを使う場合には、そのキーが対象となっている。キーに対応する値を得るには次のようなコードを書けばよい(実行結果は省略)。
sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
for item in sk:
print(item, sk[item])
このようにすれば辞書のキーにもその値にもアクセスできるが、辞書には「ビューオブジェクト」を返送する3つのメソッドが備わっている。これらを使えば、辞書のキー/値/キーと値の両者を手軽に扱える。
これらのうちのitemsメソッドを使って先ほどのコードを書き替えると次のようになる。
sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
for key, value in sk.items():
print(key, value)
このコードでは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
既に見た通り、itemsメソッドはキーと値の組を一覧するビューオブジェクトを提供するものだ(実行結果は省略)。
sk = {'first_name': 'shinji', 'family_name': 'kawasaki', 'weight': 80}
for key, value in sk.items():
print(key, value)
繰り返し処理でキーと値の両者が必要になるときには、このメソッドが便利に使えるだろう。
前回の「タプルとリストの違い」では、それらの主な用途として次のようなことを挙げた。
これに対して、辞書は「格納されているデータに名前を付けることで扱いを容易にする」「整数インデックスでは管理が難しいデータを扱う」ためのものと考えられる。
例えば、前回は名前と名字と体重を管理するためにタプルを使って、次のようなコードを書いていた。
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で示されるページの各種データ(記事名、ページビュー、筆者)をさらに辞書にまとめたものをそのキーの値とした辞書だ。このようなデータがあれば、以下のようにページビュー(値となっている辞書から「'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}')
実行結果を以下に示す。
このように、数値以外の(一意に特定可能な)何かのデータと、それに対応する値を関連付けるといったときに辞書が役に立つ。また、本稿では取り上げないが、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
この実行できないコード例では、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'}
辞書の内容を展開して関数に渡すには、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ループで取り出して、キーと値が表示されていることが分かるはずだ。
このように辞書の内容を展開して関数に渡すには「**」を前置して引数に指定することを忘れないようにしよう。特に何らかの構成値を辞書として保管しておき、それらをまとめて何かの関数に渡すといったときには、このような呼び出し方をすることになるかもしれない。
「Python入門」
Copyright© Digital Advantage Corp. All Rights Reserved.