前回の続きです。
データを柔軟に扱える「辞書 (dictionary)」を身につける
続いて辞書型 = dict型を見ていきましょう。
辞書というのは何かというと、複数のデータを格納できる棚に一つ一つ名前をつけたものというイメージです。
前回までやったリストというのは、棚というところは似ているんですが、棚の引き出しには名前がついていませんでした。
何番目みたいなインデックスで指定して取り出すという方法でした。
辞書というのはその棚の引き出しの一つに名前がついたものです。この辞書、Pythonではよく使います。
リストもよく使いますが、辞書はもっと使うと思います。特にウェブと通信するようなもの、今回のAIと通信するようなものというのは、辞書が頻繁に出てきます。
Web通信するアプリでは辞書と非常によく似たものとしてJSONというものがあります。
アプリ同士でデータをやりとりするときはJSONという形式でデータを受け渡ししたりします。
この辞書とこのJSONは非常に似通っています。Pythonでは辞書とJSONをお互いに変換することができ、変換して使うことが多いです。
また、辞書はPython3.7以降であれば並び順情報を持っています。
辞書の引き出しの一つ一つには名前を付けられるのですが、これをキーと言います。キーは重複することができません。
そして引き出しの中身、これをバリューと言います。値ですね。
キーとバリューのペアなんて言ったりしますが、このキー、つまり引き出しの名前が重複しなければいくらでもペアを持てます。
そしてネストされた辞書、辞書の中の辞書というものもできます。あるいはリストの中の辞書。辞書の中のリストという形でいろいろなパターンで書くことができます。
次の画像はAIから返ってくる応答の例ですが、ChatGPTの返答は辞書として帰ってきます。
このデータをよく見ると波括弧で始まっています。波括弧で始まるものが辞書です。
コロンの左側、これがキー、引き出しの名前です。
コロンの右側、これがバリューで引き出しの中身です。
AIチャットボットアプリではこの辞書の中から値を取り出して利用します。辞書の中にさらに辞書があったり、リストがあります。辞書の中のリストの中の辞書の中のさらに辞書みたいな入れ子構造になっています。
本書ではこのように、リストや辞書を紐解いてアプリを作っていきます。
一見難しそうに見えると思いますが、一個一個紐解いていけば理解できるようになるはずです。
ではこれから紐解いていきましょう。
辞書を定義する方法と、値の取り出し・追加・変更する方法を身につける
辞書の定義方法
それでは辞書をどうやって書くか、定義するか見ていきましょう。
辞書を定義するには、次のように波括弧の中にキー、コロン、バリュー、そして別な値があればカンマでキー、コロン、バリューというふうに続けて書きます。
{Key1: Value1, Key2: Value2, Key3: Value3, , , }
具体的には以下のようになります。
{"role": "user", "content": "こんにちは"}
このroleとかcontentというこの書き方は、今回のAIと通信するときに使う書き方です。そして同じ辞書内ではキーは重複できません。roleを2つ書くことはできないと言うことですね。
ちなみにキーというのは鍵ですね。
なお、バリューは重複は可能です。
辞書からの値取り出し方法
辞書の値を取り出す方法を確認してみます。値を取り出すには角括弧の中にキーを書きます。
prompt = {"role": "user", "content": "こんにちは"} print(prompt["role"]) # user
例えばこの辞書の中のuserを取り出したい場合、prompt[“role”] と書きます。
では、この辞書の中のcontentの中身、”こんにちは”を取り出してみましょう。
print(prompt["content"])
“こんにちは”が表示されたと思います。
このように、辞書からバリューの値を取り出すためには、角括弧の中にキーを書きます。
辞書に値を追加する方法
続いて、キー、バリューのペアを追加するには以下のように辞書のキーを指定し、変数に代入するように追加します。
prompt["message" ] = "おはようございます"
プログラムを実行してこのprompt、この辞書全体を表示してみます。
prompt = {"role": "user", "content": "こんにちは"} prompt["message"] = "おはようございます" print(prompt) # {'role': 'user', 'content': 'こんにちは', 'message': 'おはようございます'}
もし指定したキーが既に存在していればバリューが上書きされます。
prompt["role"] = "ai"
もう一回辞書全体を表示します。
{'role': 'ai', 'content': 'こんにちは', 'message': 'おはようございます'}
すると最初、roleはuserだったのですが、aiと書き変わりました。
以上、辞書を定義する文法と値を取り出す方法キー、バリューの値を追加する方法でした。
任意の値が辞書のキーやバリューの中にあるか判定する方法
次はin演算子で要素キーの有無を判定するというものを見ていきます。
in演算子というのはリストのところでもやりましたが、ある複数の要素の中に特定のものが入っているかを調べる時に使用します。
今回の例でいくと、
prompt = {'role': 'user', 'content': 'こんにちは'} result = 'role' in prompt print(result) # True
このコードは、my_dict の中の「辞書のキー」を探し出し、その有無を判定するというものです。
‘role’というキーが、my_dict にあるかないかを判定します。結果は True です。
ではcontentがあるかどうか。これも見てみましょう。
result = 'content' in prompt
結果は True です。
リストの時は、単純にリストの中の値に対してあるかないかを判定するものでしたが、辞書の場合、キーに対してあるかないかを判定します。
では、このキーに対応する中身があるかないかを判定したい場合、どうすればいいでしょうか。
バリューに対して有無を判定するには辞書の後に .values() と書きます。
prompt = {'role': 'user', 'content': 'こんにちは'} result = 'user' in prompt.values() print(result) # True
辞書のバリューの中に’user’があるかないかを判定します。実行結果はTrueとなります。
このvaluesメソッド。いったい何が返ってくるのか見てみます。
print(prompt.values())
これは、dict_values というリストのような型ですが、要はこのリストのようなものの中からあるかないかを判定していると言うことになります。
たまにこの.values() を使うことがありますので、覚えておいていただければと思います。
ちなみに、この.values() の仲間として keys() があります。これはキーだけ取り出すものです。
print(prompt.keys())
実行結果は、
‘role’と’content’ですね。keys() を省略してもキーを取得できますので使う機会はありません。
さらに、これらの仲間として .items() というものもあります。
print(prompt.items())
この場合はdict_items の中にタプルで”role”と”user”、タプルで”content”と”こんにちは”が入っています。
本書でこの .keys() とか .items() を使うことはないんですが、こういうものがあると頭の片隅に置いていただければと思います。
後でこの .keys() とか .items() や .values() を使ったコードを書いてみたいと思います。そのときまた思い出していただければ幸いです。
今回はin演算子でキーに対して有無を判定、バリューに対して有無を判定というものをみました。
以上、辞書に対してin演算子で要素の有無を判定する、でした。
ネストされた辞書からデータを取り出す方法を身につける
続いてネストされた辞書を見ていきます。これは辞書の中に辞書を持つ、というものです。
こちらの例をみると、nested_dict 変数の中身は辞書、その中にさらにキーがYamada、バリューとして辞書を持っているという構造になっています。
そして次のキー、Suzukiのバリューとして辞書を持っている。
satoというキーに対して辞書を持っている。Ikumaというキーに対して辞書を持っているという構造になっています。
そうすると、Yamadaさんという人の年齢はいくつ 住所はどこ、鈴木さんの年齢はいくつ、住所はどこ、という感じで、同じ項目を各個人によって保持できる、という使い方ができます。
では、実際にPyCharmでやってみましょう。
nested_dict = { 'Yamada': {'age': 26, 'address': 'Tokyo'}, 'Suzuki': {'age': 27, 'address': 'Yokohama'}, 'Sato': {'age': 23, 'address': 'Osaka'}, 'Ikuma': {'age': 41, 'address': 'Nagano'}, } yamada_dict = nested_dict["Yamada"] print(nested_dict['Yamada']) # {'age': 26, 'address': 'Tokyo'} print(nested_dict['Yamada']['age']) # 26
上記のような nested_dict という入れ子の辞書の中で、Yamadaさんを取り出したい場合は以下のように記述します。
yamada_dict = nested_dict["Yamada"]
この場合、{‘age’: 26, ‘address’: ‘Tokyo’}というデータが返ってきます。
さらにここからageだけを取り出したいと言う場合は、以下のようにします
print(nested_dict['Yamada']['age'])
このように、ネストされた辞書では、取り出すときに角括弧を重ねて取り出すことができます。
ではSatoさんをやってみましょう。
sato_address = nested_dict["Sato"]["address"] print(sato_address) # Osaka
nested_dictの中のSatoというキーの中のaddressというキーの中身が取り出せるわけです。
キーとしてSatoさんのアドレスを指定していますので、Osakaと表示されます。
これはぜひあなたご自身でもいろいろなパターンで練習してみてください。
この話がどこに活きてくるかというと、ChatGPTとのやりとりの中です。
実際に作っていくアプリAIとやりとりするときのAIの応答は、次のように辞書として返ってきます。
このAIからの返答の辞書は、入れ子になっています。
辞書のchoicesという中にはリストがあって、そのリストの中にはさらに辞書があります。
その辞書のmessageというキーの中にバリューとしてさらに辞書を持っている、という構造になっています。
こういうAIの返答からデータを取り出すときに、今の考え方が活きてきます。
今は辞書の中の辞書を取り出すということだったんですが、後で辞書の中のリストのデータというように、リストの中の辞書、あるいは辞書の中のリストの取り出し方を見ていきます。
以上、ネストされた辞書でした。
getメソッドで辞書から値を取り出すことでエラーを回避する方法
もうひとつ重要なトピックとして、getメソッドで辞書から値を取り出す方法があります。
WEBアプリと何かやりとりするときに起こりがちな問題を避けるためにgetメソッドを使います。
辞書に対してこの角括弧を使ってキーを指定するという取得方法は、キーが存在しないときにエラーになってしまいます。
このエラーを避けるためには、getメソッドというものを使う必要があります。
例えば以下のコードがあったとします。
my_dict = { "name": "John", "age": "36", "country": "Norway" } age = my_dict["age"] print(age) # 36
ここまでは特に問題ないかと思います。問題は以下のように、存在しないキーを指定したときです。
city = my_dict["city"] print(city)
このコードは次のように KeyError になってしまいます。
これはWEBアプリに対してAPIを叩いてデータを取得しようとしたとき起こりがちな問題です。APIを叩くというのは、WEBアプリケーションと通信する、という意味ですね。
実行時の条件によっては辞書の中に指定したキーがあったりなかったり、という状態がありますので角括弧を使った書き方だとエラーになる可能性があります。
ではどうするか? ここでgetメソッドを使用します。
my_dict = { "name": "John", "age": "36", "country": "Norway" } age = my_dict["age"] print(age) # 36 city = my_dict.get("city") print(city) # None
実行結果は、Noneです。
このNoneを表示したくない場合は、第2引数に例えば空白文字を指定すると、cityのキーがない場合実行結果は空白文字にできます。
city = my_dict.get("city", "") print(city) # 実行結果は空文字
つまり、キーが存在しないときのデフォルト値を指定しているわけです。
以下のようなデフォルト値を指定することもできます。
city = my_dict.get("city", "N/A") print(city) # N/A
その時々の状況によって何をデフォルト値にするか決めることができます。
以上、getメソッドで辞書から値を取り出す方法でした。
角括弧ではなくgetメソッドを使うことで、キーが存在しないときに起きるエラーを回避することができます。
これはWEBアプリとやりとりするときに起こりがちなエラーを回避するためによく使いますので覚えておきましょう。
リストの中の辞書、辞書の中のリスト、辞書の中のリストの中の辞書から値を取り出す方法
次はリストの中の辞書、辞書の中のリストを覚えましょう。
先にお話ししたように、リストの中の辞書とか辞書の中のリストというパターンは、APIの戻り値としてよく見られます。
今回作るアプリのAIからの応答は次のように返ってきます。
複雑そうに見えますが、ひとつひとつ順を追って取り出していけば理解していただけると思います。
リストの中の辞書
では、まずリストの中の辞書、こちらを見てみます。
これは list_of_dicts という名前の変数にリストがあり、その中に辞書があり、name, age, occupationというキーを持っています。occupation は職業です。
list_of_dicts = [ {"name": "Alice", "age": 25, "occupation": "Engineer"}, {"name": "Bob", "age": 30, "occupation": "Doctor"}, {"name": "Charlie", "age": 35, "occupation": "Lawyer"}, ]
こうするとそれぞれ3人分のデータを同じキーを持つ構造として保持することができます。
キーがかぶっているように見えますが、この辞書のひとつひとつは独立していますので、同じ構造として定義することができます。
ここからアリスさんを取り出してみます。
alice = list_of_dicts[0] print(alice)
こうするとアリスさんの辞書が取り出されます。
これを、
alice = list_of_dicts[0] print(alice["age"])
で、角括弧で”age”とすると25というバリューを取り出すことができます。
では次はボブさんのoccupationやcharieさんの名前を取り出してみましょう。
bob = list_of_dicts[1] charlie = list_of_dicts[2] print(bob["occupation"]) print(charlie["name"])
このようにひとつひとつリストの中の要素として取り出すことも可能ですが、次のように一気に取り出す方法があります。
alice_age = list_of_dicts[0]["age"] print(alice_age)
アリスさんはリストの0番目の要素ですね。そしてアリスさんの辞書の中のageを角括弧で重ねて書くことで、一度に値を取り出すことができます。
アリスさんのageのバリューである25を取り出すと言うことになります。
ボブさん、チャーリーさんでも同じように書いてみてください。
辞書の中のリスト
次は辞書の中のリストを見ていきます。
辞書の中にキーがあって、そのキーのバリューとしてリストを持っているという構造です。
では以下のコードもやってみましょう。
dict_of_list = { "fruits": ["apple", "banana", "cherry"], "vegetables": ["potato", "tomato", "onion"], "meet": ["chicken", "beef", "pork"], } fruits_list = dict_of_list["fruits"] print(fruits_list) # ['apple', 'banana', 'cherry']
ここからバナナを取り出したい場合、どう書くか分かるでしょうか?
fruits_list = dict_of_list["fruits"] print(fruits_list[1])
fruits_listの1番目の要素、バナナを取り出すには上記のように[1]とします。
だんだんわかってきましたでしょうか。同じようにベジタブルもやってみましょう。
vegetables_list = dict_of_list["vegetables"] print(vegetables_list)
上記コードで、[‘potato’, ‘tomato’, ‘onion’]と表示されます。
この vegetables_list から onion を取りだしてみましょう。リストの最後の要素を取り出すときは、-1とするのでした。
print(vegetables_list[-1])
上記のように指定すると最後の値onionが返ってきます。
ではミートもやってみましょう。
meat_list = dict_of_list["meet"] print(meat_list[1])
このように書くと何が取り出されるでしょうか。beefですね。
上記のように一度リストとして取り出した後に要素のインデックスを指定する、という2回に分けてやってもいいのですが、以下のように一度で取り出せます。
beef = dict_of_list["meet"][1]
キーを指定するとリストが値として返ってきますので、このリストに対して1番目の要素、[1]とすると”beef”が指定されます。
あなたご自身でいろいろな値を一度で取り出せるかやってみてください。
辞書の中のリストの中の辞書
では次は辞書の中のリストの中の辞書を見てみましょう。ちょっと複雑になりますが、考え方は今までと一緒です。
dict_of_list_of_dictsという辞書があり、辞書のキーとしてemployeesとstudentsがあります。バリューはリストです。
そのリストの中に、name, age, occupationというキーを持つ辞書が入っています。
このような構造になっています。
なお、employeesは社員という意味です。
社員さんもリストをこのように保持でき、学生さんのリストもnameやageという共通項目を持てます。
非常に柔軟なデータ構造です。
APIからの応答がこのような辞書とリストの組み合わせなのは、柔軟なデータ構造が持てるという理由が大きいのです。
ではこちらを書いていきましょう。
dict_of_list_of_dicts = { "employees": [ {"name": "John", "age": 30, "city": "New York"}, {"name": "Ford", "age": 40, "city": "London"}, ], "students": [ {"name": "Alice", "age": 20, "city": "Paris"}, {"name": "Bob", "age": 25, "city": "Tokyo"}, ] }
ではチャレンジ問題です。
社員さん(employees)のFordさんのCity、LondonをPrint()で表示させてみましょう。
一度、この先を見ずに、実際にチャレンジしてみてください。
では解答例を書いてみます。
london = dict_of_list_of_dicts["employees"][1]["city"] print(london)
まずFordさんを取り出したいので、キーを”employees”にします。するとバリューであるリストが返ってきます。
Fordさんはリストの1番目ということですから、1とします。
そうすると {“name”: “Ford”, “age”: 40, “city”: “London”}
という辞書が返ってきますので、キー”city”と指定するとLondonが表示されます。
次のようにわけて書くこともできます。
ford = dict_of_list_of_dicts["employees"][1] print(ford["city"])
あるいは辞書として受け取ってから”city”と指定して、分けて書いても大丈夫です。
employees = dict_of_list_of_dicts["employees"] print(employees[1]["city"])
いろんなパターンで書けます。
ということでリストの中の辞書、辞書の中のリストを見てきました。
AIからの応答で任意の値を取り出したいというときには、ここで学んだ段階を追って取り出していくというふうに取り出しながら、最後にまとめ上げていくというようにすることになります。
複雑そうに見えますが、ひとつひとつの要素、考え方は単純です。
どういうキーを指定すれば何が返ってくるか、リストが返ってくるのであれば、その中の何番目の要素を取り出すのか。
そしてリストの中にさらに辞書が入っていた場合、その辞書の何のキーを取り出したいのかといったように、順を追ってひとつずつ紐解いていけば、答えに辿り着くことができます。
ぜひこの考え方、身に着けていただければと思います。