見出し画像

pythonプログラム初歩の初歩5/辞書の話

こんにちはmakokonです。
今回もデータ構造についての話です。
pythonは何にでも使える便利なプログラム言語なのですが、その力の真価を発揮するためには、その豊富なデータタイプを理解し、適切に使いこなすことが必要です。その中でも特に重要なのが、『辞書』と呼ばれるデータタイプです。じっさい、私もそんな機能が標準で付いているなんて、とても便利だと感じました。このブログでは、ごく初歩的な辞書の使い方を学べますが、それだけでpython力がぐっと向上すると思いますよ。



辞書とは


辞書は、キーと値のペアを保持するデータ構造です。まるで、実際の辞書が単語(キー)とその定義(値)をペアにしているのと同じです。
Pythonの辞書は非常に強力で、データの操作や管理を効率的に行うことができます。
この特性は、リストやタプルとは異なります。リストやタプルでは、要素へのアクセスはインデックスを通じて行われますが、辞書ではキーを使って値にアクセスします。
インデックスを通じてアクセスするとは、”何番目のデータを調べる”とか”順番にデータを調べる”のように、データの格納されている順番が重要な操作です。辞書では”キー”を通じてデータを調べるので、順番は重要でははありません。

辞書の構造

辞書の構造は、以下のようになっています。
dict1 = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
つまり 『キー:データ』の組み合わせが続くデータです。
辞書なのでキーは、ユニーク(一意)である必要がありますが、データの方はあらゆるデータ構造を利用することができます(多分)。
まず構造を実際に確認しましょう。

# 辞書の作成
dict1 = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
print(dict1)

{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

出力結果

定義したとおりに、出力されます。小さいことなんですが、複雑なデータ構造を持つ変数がそのまま、出力されるのはpythonの素晴らしい長所だと思います。

辞書の基本操作1 表示、追加、削除

では、今作った辞書dict1を用いて、基本操作を確認しましょう。
確認する操作は、値の取得、値の設定(追加)、キーの存在チェック、キー(ペアのデータも)の削除です。

  • key1に対応する値を表示します。

  • key4を追加します。

  • 辞書にkey4があるかどうかを確認します。

  • 辞書にkey5があるかどうかを確認します。

  • key1を辞書から削除します

# 辞書の作成
dict1 = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
print(dict1)
# 値の取得
print(dict1['key1'])  # 'value1'
# 値の設定
dict1['key4'] = 'value4'
# キーの存在チェック
print('key4' in dict1)  # True
print('key5' in dict1)  # False
# キーと値のペアの削除
del dict1['key1']

print(dict1)

{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
value1
True
False
{' 'key2': 'value2', 'key3': 'value3', 'key4': 'value4'}

出力結果

なるほど、辞書内にどういうデータがあるか、データを追加する方法などすべてキーだけがあればできそうですね。
なお、値の設定で、key4がなかったので、key4が追加されていますが、例えばdict1['key1']='value1_1' などとするとkey1に対応するデータが、書き換わります。決してkey1が2つに増えたりはしません。キーはユニークですね。

辞書の基本操作2 存在しないキー

辞書に存在しないキーにアクセス

# 辞書の作成
dict1 = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
print(dict1['key1'])
print(dict1['key4'])

value1
Traceback (most recent call last):
File "xxx.py", line xx, in <module>
print(dict1['key4'])
KeyError: 'key4'

出力結果

辞書に存在しないキーにアクセスしたらどうなるか、確認しましょう。
上記コードを実行するとkey1に対応するダータは、表示されますが、key4はないのでKeyError:'key4'を出して止まってしまいます。もちろん、いい加減な回答をしてもらっては困るのですが、いちいち止まってしまうのも困りものです。


存在しないキーでエラーを出さない方法2つ

このエラーを回避する方法は素直に考えると2つあります。
方法1:データにアクセスする前に、指定のキーが存在するかどうかを確認する。存在しなければ、メッセージを出すなど例外処理をする。
方法2:get()メソッドを利用する。指定したキーが存在しない場合、予め指定した特別な値を返す。
簡単なコードはこちら。

# 辞書の作成
dict1 = {"key1": "value1", "key2": "value2", "key3": "value3"}

# 方法1 キーの存在を確認
if 'key4' in dict1:
    print(dict1['key4'])
else:
    print("指定のキーが存在しない")
    
# 方法2 キーが存在しない場合にはNoneを返す
value = dict1.get('key4', None)
print(value)

方法1のキーの存在を確認し、存在しなければ例外処理を記述するのは確実な方法です。ユーザーも開発者も何が起こっているのかを明確に確認することが可能で、デバッグやデータの取得システムの改善など対応が明確になります。しかし、一方で、わざわざ例外処理を記述することでプログラムが煩雑になります。キーが存在する場合も、すべて存在チェックを通るので、実行時間増えるし、プログラムの見通しも悪くなります。

一方方法2のキーが存在しないときには、データとして特別な値として'None'を返すやり方はスマートです。get()メソッドを使うことで、一行でキーの存在チェックとデータの取得が同時にできるのでプログラム自身も非常に読みやすくなります。
問題は、特別な値が本当に例外的な値なのか、それとも普通に辞書に含まれるデータなのかを区別できないことです。また、キーが存在しなかった事実を知ることもできません。したがって、エラーの存在が非常に分かりにくくなります。十分な検証を行った上で、どうしても対応できないキーが存在することを明確にした上で、使うべきでしょう。
ただ、pythonプログラムではこの方法2が圧倒的に使われているようです。

辞書の基本操作3 辞書全体のデータ

すべてのデータの取得

すべてのキーを取得、すべての値を取得、すべてのキーと値のペアを取得

# 辞書の作成
dict1 = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
print(dict1)
# 辞書の全キー取得
print(dict1.keys())  # dict_keys(['key2', 'key3', 'key4'])
# 辞書の全値取得
print(dict1.values())  # dict_values(['value2', 'value3', 'value4'])
# キーと値のペアの取得
print(dict1.items())  # dict_items([('key2', 'value2'), ('key3', 'value3'), ('key4', 'value4')])
exit()

{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
dict_keys(['key1', 'key2', 'key3'])
dict_values(['value1', 'value2', 'value3'])
dict_items([('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')])

実行結果

プリント結果の内容は、一目瞭然でしょうが、ちょっと見慣れない形ですね。なんかいきなり名前がついているし、リストのようなタプルのような
怪しい形です。調べてみると、dict1.keys(), dict1.values(), dict1.items() の戻り値は、それぞれ dict_keys, dict_values, dict_items という特殊なデータ形式を返します。そのまんまですね。専用のダータタイプということでしょう。
dict.keys()の戻り値は dict_keys 型
dict.values()の戻り値は dict_values 型
dict.items()の戻り値は dict_items 型

これらはリストやタプルとは異なる型で、辞書のビューを提供します。
つまり、Pythonの辞書ビューオブジェクトで、辞書のエントリを動的に表示します。つまり、辞書が変更されたとき、これらのビューオブジェクトもそれを反映します。しかし、これらは変更不能(immutable)で、リストのように要素を追加したり削除したりすることはできません。

辞書ビューの要素を取り出す。

動的に辞書情報を反映しているが、変更不能なのは便利なのか不便なのかわかりませんが、ともかくリストみたいな形なので、リストと同様にその要素を取り出すことができます。要素を追加したりすることはできませんけど。

# 辞書の作成
dict1 = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

# キーと値のペアの取得
items = dict1.items()

# itemsの各要素(キーと値)を一つずつ取り出す
for key, value in items:
    print(f'Key: {key}, Value: {value}')

Key: key1, Value: value1
Key: key2, Value: value2
Key: key3, Value: value3

実行結果

まあ、普通に扱えましたね。参照するだけならこれで行けるでしょう。

辞書ビューを普通のリストにする。

それでも、辞書の内容を参照するだけでなく、(辞書は変更せずに)加工したいこともあるでしょう。だから、辞書ビューを普通のリストに変換することによって、変更可能なオブジェクトになります。その代わりに辞書の変更に伴う動的な反映はなくなります。

# 辞書の作成
dict1 = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

# 辞書の全キー取得し、リストに変換
keys = list(dict1.keys())
print(keys)  # ['key1', 'key2', 'key3']

# 辞書の全値取得し、リストに変換
values = list(dict1.values())
print(values)  # ['value1', 'value2', 'value3']

# キーと値のペアの取得し、リストに変換
items = list(dict1.items())
print(items)  # [('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')]

['key1', 'key2', 'key3']
['value1', 'value2', 'value3']
[('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')]

実行結果

普通のリストになりました。これで辞書情報の管理もやりやすくなりました。

辞書内容をソートして閲覧する

辞書ビューをリストにする前に予め、ソートして見やすくしておくこともできます。(辞書そのものの内容をソートするわけではありません)


# 辞書の作成
dict1 = {'key3': 'value3', 'key1': 'value1', 'key2': 'value2'}

# キーに基づいてソート
sorted_keys = sorted(dict1.keys())
print("sorted key")
for key in sorted_keys:
    print(f'{key}: {dict1[key]}')

# 値に基づいてソート
sorted_values = sorted(dict1.items(), key=lambda x: x[1])
print("sorted value")
for key, value in sorted_values:
    print(f'{key}: {value}')
    
    

sorted key
key1: value1
key2: value2
key3: value3
sorted value
key1: value1
key2: value2
key3: value3

実行結果

出来上がったsorted_keys、sorted_valuesは普通のリストです。
また、sorted(dict1.items(), key=lambda x: x[1]) はiitemsを1番の要素(0番から始まるので、valueになります)の値でソートする意味になります。

辞書の基本操作4 文字列以外でもいい 

今まで、辞書の内容が文字列 'key':'value'の形式で扱っていましたが、それ以外の例をいくつか上げて、今回の記事を締めくくりましょう。

辞書の中身がリスト

普通に考えても『あなたはどういう人ですか?』と聞かれて、『私は20歳の男性で、身長は160cm,体重は150kgです。』とか、『趣味は海釣りで毎週でかけています』とか答えるものです。趣味云々のあたりは何を答えるか、どのように答えるか人それぞれですが、前半の属性部分は大体決まっているようなものです。そういう決まりきった属性は、文字列でダラダラ書かれるより、分類されたデータタイプのほうが便利ですね。

# 辞書1: 人名に対し、値として[性別、年齢、身長、体重]のリストが決められている
dict1 = {
    'Alice': ['Female', 20, 160, 50],
    'Bob': ['Male', 22, 175, 70],
    'Charlie': ['Male', 25, 180, 80]
}

# Aliceの情報を取得
pirnt("アリスの情報は")

print(dict1['Alice'])  # ['Female', 20, 160, 50]

# Bobの身長を取得
pirnt("ボブの身長は")
print(dict1['Bob'][2])  # 175

アリスの情報は
['Female', 20, 160, 50]
ボブの身長は
175

実行結果

この例では、キーを人名にして、値を[性別、年齢、身長、体重]からなるリストにしています。
情報がリストになっているので、ボブの身長はprint(dict1['Bob'][2])で調べることができます。
ただ、この使い方だと[2]が身長であることが人目で分からずプログラムの可読性が下がります。
そういうときは次の

辞書の中身が辞書

さっきの例と同じデータですが、辞書の構造を変えます。

# 辞書2: 人名に対し、値として{性別、年齢、身長、体重}の辞書が決められている
dict2 = {
    'Alice': {'gender': 'Female', 'age': 20, 'height': 160, 'weight': 50},
    'Bob': {'gender': 'Male', 'age': 22, 'height': 175, 'weight': 70},
    'Charlie': {'gender': 'Male', 'age': 25, 'height': 180, 'weight': 80}
}

# Aliceの情報を取得
print("アリスの情報は")
print(dict2['Alice'])  # {'gender': 'Female', 'age': 20, 'height': 160, 'weight': 50}

# Bobの身長を取得
print("ボブの身長は")
print(dict2['Bob']['height'])  # 175

アリスの情報は
{'gender': 'Female', 'age': 20, 'height': 160, 'weight': 50}
ボブの身長は
175

実行結果

今度は、値の部分を{'gender': 'Female', 'age': 20, 'height': 160, 'weight': 50}のように、辞書形式にしてあります。
これによって、身長を知りたいときにdict2['Bob']['height']のように書くことができて、[2]のような謎のナンバーでなく、['height']のようにああ、身長を見たいんだなとすぐに分かります。

使い分け

では、この2つをどのように使い分けたらいいのでしょうか。上記の例だと辞書方式のほうが明らかに使いやすい気がしますが、それぞれいいことと悪いことがあります。
辞書の中にリスト:リストは順序があります。そのため、順序や順番が重要な情報を扱う場合に適しています。例えば、ある人物の年齢変化を追跡する場合などに利用することができます。ただし、この方法の欠点は、各値の意味を覚えておかなければならない点です。例えば、リストの最初の値が性別で、2番目の値が年齢であるといった具体的な情報はコードからは読み取れないため、誤解を招く可能性があります。
辞書の中に辞書:こちらは、各値に名前をつけることができるため、情報が何を意味するのかが明確になります。これにより、コードの可読性が向上します。また、順序が重要でない情報を扱うのに適しています。ただし、この方法の欠点は、データの並びに意味がある情報を、正しく扱えないことと、実際居運用するに当たって、各辞書に同じキーが存在していることを保証する必要があります。もし、ある人物には身長データが無いのに、全員の身長データを調べようとすると、エラーが出ます。

辞書の内容を更新する。

今までの例では、辞書の内容の更新はキーの追加、キーの削除、既存キーの書き換えでしたが、その変更内容は予めわかっていたことでした。『key4を足すなら最初からkey4のある辞書を作っておけよ』ですね。
ここでは、現在の辞書の内容に応じて、辞書を更新する例を紹介します。
課題「ある長文データが文字列で与えられたときに、単語の使用頻度を調べよ。長文は英語なので、単語は空白文字またはピリオド、コンマで区切られている。また大文字、小文字は区別しないものとする。
検査用文字列:"This is a test. This is only a test. Testing, one, two, three."

def count_words(text):
    # ピリオドやコンマをスペースに置換し、全て小文字に変換
    text = text.replace(".", " ").replace(",", " ").lower()

    # 文字列を単語に分割
    words = text.split()

    # 単語の出現頻度を計算
    word_count = {}
    for word in words:
        if word not in word_count:
            word_count[word] = 1
        else:
            word_count[word] += 1

    return word_count

text = "This is a test. This is only a test. Testing, one, two, three."

print(count_words(text))

{'this': 2, 'is': 2, 'a': 2, 'test': 2, 'only': 1, 'testing': 1, 'one': 1, 'two': 1, 'three': 1}

実行結果

このプログラムでは、与えられた文字列を以下の順で処理します。
def count_words(text):

  1. 区切り文字をすべてスペースに変換

  2. すべて小文字に変換

  3. split()関数を利用して、全単語リストを作成

  4. 単語を数えるからの辞書を作成

  5. ループ(リスト中のすべての単語に対し)

  6.  その単語がなければ、新しく辞書にその単語をキーにカウント1で登録

  7.  その単語があれば、その単語のキーをカウント+1に更新

main:
検査文をセットして、count_wordをよびだす。

まとめ

この記事では、Pythonで辞書を扱うための基本的な知識を学びました。まず、キーと値のペアからなる辞書の構造とその基本的な操作方法について確認しました。次に、辞書に存在しないキーへのアクセスが発生した場合の問題と、その問題を回避するための方法を学びました。

さらに、辞書全体のデータを取得する方法や、辞書ビューという特殊なデータ構造の取り扱い方について学びました。具体的には、辞書ビューを普通のリストに変換する方法や、辞書の内容をソートして閲覧する方法を説明しました。

また、辞書の値が文字列だけでなく、リストや別の辞書である場合の扱い方についても紹介しました。それぞれのデータ構造の長所と短所を理解し、適切な使い分けができるようになりました。

最後に、ワードカウントを例に、辞書内の数値データの更新方法について学びました。これらの知識を活用することで、Pythonの辞書をより効果的に使用することができるようになります。

おまけ タイトル画の説明 by GPT-4V

この画像は、"ILLUSTRATED PYTHON"と題された架空の本を描いたイラストです。本の上には、Pythonプログラミング言語を象徴する緑色の蛇が巻きついており、その周辺にはリンゴやチェリーなどのフルーツが散らばっています。背景には電卓、オープンしたノートブック、本棚に並ぶ書籍があります。イラストのスタイルは手書き風で、ポップでカラフルな配色が特徴的です。また、本には中国語らしき文字も見受けられ、技術的な書籍または教科書を想起させるデザイン要素が含まれています。全体的に、学習や教育に関連するテーマの画像であることが推測されます。

#Python #辞書データ #基本操作 #エラー回避 #データ取得 #辞書ビュー #リスト変換 #ソート #リスト #ネスト辞書 #使い分け #データ更新 #プログラム #初歩の初歩

この記事が気に入ったらサポートをしてみませんか?