はじめに
普段は物流トラックドライバーをしながらPython学習をしている tosane932 です。
僕がPython学習を始めた初期から現在進行形で開発している、
🍞 sales_data_app 🥖
の実際のコードを教材に、Python基礎を学び直しています。
これまで、
1時限目
変数・データ型・比較演算子・if文
2時限目
リスト・辞書・set・for文
3時限目
関数・引数・return・関数の呼び出し・型ヒント
4時限目
モジュール・import・例外・try・except
5時限目
クラス・インスタンス・__init__・self・属性
と学び直してきました。
6時限目も、新しい教材を探すために別のコードを書くわけではありません。
すでに何度か見てきた app.py を、もう一度読んでみます。
現在の sales_data_app ではGuest Demoや店舗メモツールなど機能も増えていますが、今回もPython基礎に集中するため、AIアドバイスを作る処理の一部分だけを教材にします。
🐹「よし、じゃあ app.py の _generate_ai_advice() を開いてみよう📚️」
そこには、こんな1行があります。
sales_summary = ", ".join(
[f"{name}: {qty}個" for name, qty in ranked_sales]
)
🐹「……1行にいろいろ入りすぎでは?」
よく見ると、
f"{name}: {qty}個"
for name, qty in ranked_sales
[ ... for ... ]
", ".join(...)
と、まだ詳しく学んでいない書き方がいくつも入っています。
今回はこの1行を分解しながら、
f文字列・アンパック・リスト内包表記・join
について学び直していきます。
🍞 Guest Demoを公開しています
sales_data_app のGuest Demoを公開しています。
この記事で扱っている売上データやAIアドバイスを、実際の画面から確認できます。
https://bakery-salesdata.onrender.com/
Render上で公開しているため、初回アクセス時は画面が表示されるまで少し時間がかかる場合があります。
今回のコードはどこにある?
今回使うコードは、
sales_data_app
↓
app.py
↓
_generate_ai_advice()
の中にあります。
現在の関数は、Guest利用時の情報も扱えるように、
def _generate_ai_advice(ranked_sales, current_dataset=None):
という形になっています。
今回は、
ranked_sales
から sales_summary を作るPython基礎だけに集中するため、
current_dataset=None
については深入りしません。
今回読む実コードはこちらです。
sales_summary = ", ".join(
[f"{name}: {qty}個" for name, qty in ranked_sales]
)
3時限目では、このあとにある、
prompt = build_sales_prompt(sales_summary)
から、
sales_summaryを作る
↓
build_sales_prompt()へ渡す
↓
AIへ送るプロンプトを作る
という流れを見ました。
今回は、
sales_summary = ...
の右側で何をしているのか
をさらに細かく見ていきます。
🐹「前に通った場所だけど、まだ掘ってなかったところか。」
🍞 それでは6時限目
今回のテーマ
「f文字列・アンパック・リスト内包表記・joinとは?」
今回学び直すのは4つです。
1. 📝 f文字列とは?
まずは、この部分です。
f"{name}: {qty}個"
先頭に、
f
があります。
これは**f文字列(f-string)**と呼ばれる書き方です。
例えば、
name = "メロンパン"
qty = 12
という変数があるとします。
普通の文字列なら、
"メロンパン: 12個"
と直接書けます。
でも、商品名や販売数は毎回同じとは限りません。
そこで、
f"{name}: {qty}個"
と書くと、
{name}
↓
nameの中身
{qty}
↓
qtyの中身
が文字列へ入ります。
結果は、
メロンパン: 12個
です。
🐹「文字列の中に変数を差し込めるんだ。」
{}の中に値を書く
もう少し簡単な例なら、
bread = "あんぱん"
price = 180
message = f"{bread}は{price}円です"
と書けます。
message の中身は、
あんぱんは180円です
になります。
f"..."
↓
f文字列
{bread}
↓
変数breadの値を入れる
{price}
↓
変数priceの値を入れる
という形です。
🍞 sales_data_appでは?
今回の実コードでは、
f"{name}: {qty}個"
です。
例えば、
name = "メロンパン"
qty = 12
なら、
メロンパン: 12個
になります。
そして、
name = "あんぱん"
qty = 7
なら、
あんぱん: 7個
になります。
つまり、この部分では、
商品名と販売数を、AIへ渡しやすい文字列へ変えている
と読めます。
2. 📦 アンパックとは?
次は、
for name, qty in ranked_sales
です。
2時限目では、
for
を使って、リストなどから値を1つずつ取り出す処理を見ました。
例えば、
breads = ["メロンパン", "あんぱん"]
for bread in breads:
print(bread)
なら、
メロンパン
あんぱん
と1つずつ取り出せます。
でも今回のコードは、
for name, qty in ranked_sales
となっています。
変数が、
name
qty
と2つあります。
🐹「forの後ろに2人いる。」
ranked_salesはどこから来る?
ここで、実コードをもう少しだけ前へ戻ってみます。
AIアドバイスを呼び出す処理では、売上データから、
ranked_sales = sorted(
sales_data.items(),
key=lambda item: item[1],
reverse=True,
)
という形で ranked_sales を作っています。
今回は sorted() や lambda を詳しく学ぶ回ではないため、ここでは、
商品名と販売数を、販売数の大きい順に並べている
くらいに読んで進みます。
🐹「また知らない lambda がいるけど、今日は追いかけない。」
注目したいのは、
sales_data.items()
です。
辞書の .items() では、
キー
値
を1組ずつ取り出せます。
例えば、
sales_data = {
"メロンパン": 12,
"あんぱん": 7,
}
なら、
("メロンパン", 12)
("あんぱん", 7)
のような組み合わせとして扱えます。
そのため ranked_sales も、イメージとしては、
[
("メロンパン", 12),
("あんぱん", 7),
]
のような、
商品名
販売数
の組み合わせになります。
ここでタプルが出てくる
("メロンパン", 12)
のように、
商品名
販売数
が2つで1組になっています。
この、
("メロンパン", 12)
のようなデータはタプルです。
2つの値を2つの変数へ取り出す
例えば、
name, qty = ("メロンパン", 12)
とすると、
name
↓
"メロンパン"
qty
↓
12
と分かれます。
このように、
まとまっている複数の値を、それぞれの変数へ取り出す
ことを、今回はアンパックとして覚えます。
実コードの、
for name, qty in ranked_sales
でも同じです。
まず、
("メロンパン", 12)
を取り出して、
name = "メロンパン"
qty = 12
となります。
次に、
("あんぱん", 7)
を取り出して、
name = "あんぱん"
qty = 7
となります。
流れにすると、
ranked_sales
↓
("メロンパン", 12)
↓
name = "メロンパン"
qty = 12
↓
("あんぱん", 7)
↓
name = "あんぱん"
qty = 7
です。
🐹「2時限目のforが、2つずつ取り出す形になったんだ。」
3. 🔁 リスト内包表記とは?
次は、この部分を見ます。
[f"{name}: {qty}個" for name, qty in ranked_sales]
🐹「カッコの中にforが入ってる。」
これはリスト内包表記と呼ばれる書き方です。
簡単な形なら、
[処理 for 値 in 繰り返すもの]
のように書けます。
例えば、
numbers = [1, 2, 3]
から、
[number * 2 for number in numbers]
とすると、
[2, 4, 6]
という新しいリストを作れます。
普通のfor文で書くと?
同じ処理を、これまで学んだ for を使って書くと、
result = []
for number in numbers:
result.append(number * 2)
のように書けます。
結果は、
[2, 4, 6]
です。
リスト内包表記では、
[number * 2 for number in numbers]
と、短く書けます。
リスト内包表記は、
必ず短く書いた方が良い
というものではありません。
まずは、
forで値を取り出す
↓
処理する
↓
新しいリストを作る
という流れを読めることを目標にします。
🍞 sales_data_appのリスト内包表記
実コードへ戻ります。
[f"{name}: {qty}個" for name, qty in ranked_sales]
少し分けると、
for name, qty in ranked_sales
↓
商品名と販売数を取り出す
そして、
f"{name}: {qty}個"
↓
文字列を作る
となります。
ranked_sales が、
[
("メロンパン", 12),
("あんぱん", 7),
]
なら、
1回目は、
f"{name}: {qty}個"
から、
メロンパン: 12個
ができます。
2回目は、
あんぱん: 7個
ができます。
その結果、
[
"メロンパン: 12個",
"あんぱん: 7個",
]
という新しいリストになります。
🐹「元は商品名と数字だったのに、文字列のリストになった。」
普通のfor文へ戻してみる
今回のリスト内包表記を、
これまで学んだ形へ戻すと、イメージとしてはこうです。
sales_items = []
for name, qty in ranked_sales:
sales_items.append(f"{name}: {qty}個")
すると、
sales_items
は、
[
"メロンパン: 12個",
"あんぱん: 7個",
]
になります。
今回の、
[f"{name}: {qty}個" for name, qty in ranked_sales]
は、この処理を短くまとめた形です。
4. 🔗 joinとは?
ここまでで、
[
"メロンパン: 12個",
"あんぱん: 7個",
]
というリストができました。
でも実コードは、まだ外側に続いています。
", ".join(...)
です。
join は、
複数の文字列を、1つの文字列につなげる
ときに使えます。
簡単な例
例えば、
breads = [
"メロンパン",
"あんぱん",
"クロワッサン",
]
があるとします。
これを、
", ".join(breads)
とすると、
メロンパン, あんぱん, クロワッサン
という1つの文字列になります。
ここで、
", "
が、文字列と文字列の間に入っています。
メロンパン
+
", "
+
あんぱん
+
", "
+
クロワッサン
というイメージです。
joinは区切り文字側に書く
最初に見ると少し不思議なのが、
", ".join(breads)
という順番です。
", "
↓
何を間に入れるか
.join(...)
↓
中の文字列をつなげる
となります。
つまり、
", ".join(...)
なら、
カンマ+半角スペースでつなぐ
という意味になります。
🍞 sales_data_appでは?
今回のコードでは、
", ".join(
[f"{name}: {qty}個" for name, qty in ranked_sales]
)
です。
内側では、
[
"メロンパン: 12個",
"あんぱん: 7個",
]
というリストを作ります。
それを、
", ".join(...)
すると、
メロンパン: 12個, あんぱん: 7個
という1つの文字列になります。
そして、
sales_summary = ...
によって、
sales_summary
へ代入されます。
🔗 4つを全部つなげてみる
今回の実コードを、もう一度見てみます。
sales_summary = ", ".join(
[f"{name}: {qty}個" for name, qty in ranked_sales]
)
最初はかなり詰まって見えます。
でも、こういうコードは一番内側から外側へ読むと追いやすくなります。
ranked_sales
↓
("メロンパン", 12)
("あんぱん", 7)
↓
for name, qty in ranked_sales
↓
nameとqtyへアンパックする
↓
f"{name}: {qty}個"
↓
"メロンパン: 12個"
"あんぱん: 7個"
↓
リスト内包表記でリストにする
↓
[
"メロンパン: 12個",
"あんぱん: 7個",
]
↓
", ".join(...)
↓
"メロンパン: 12個, あんぱん: 7個"
↓
sales_summaryへ代入
となります。
🐹「1行だけど、中では結構働いてる。」
これまでの書き方へ戻すと?
今回の1行を、
これまで学んだ for を使って少し長く書くと、
sales_items = []
for name, qty in ranked_sales:
sales_items.append(f"{name}: {qty}個")
sales_summary = ", ".join(sales_items)
というイメージになります。
そして実コードでは、
sales_summary = ", ".join(
[f"{name}: {qty}個" for name, qty in ranked_sales]
)
とまとめています。
短く書かれていても、
for
↓
値を取り出す
アンパック
↓
nameとqtyへ分ける
f文字列
↓
文字列を作る
リスト内包表記
↓
新しいリストを作る
join
↓
1つの文字列につなげる
と分解すると、これまで学んだ基礎とつながって見えてきます。
🤖 作った文字列はどこへ行く?
ここで作った、
sales_summary
は、そのまま終わるわけではありません。
すぐ後ろで、
prompt = build_sales_prompt(sales_summary)
と使われています。
3時限目で見た、
build_sales_prompt(...)
です。
流れにすると、
sales_data
↓
商品名と販売数の組み合わせを作る
↓
販売数の大きい順にranked_salesへ並べる
↓
nameとqtyへアンパックする
↓
f文字列へ変換する
↓
joinで1つにまとめる
↓
sales_summary
↓
build_sales_prompt(sales_summary)
↓
AIへ渡すプロンプトを作る
となります。
例えば、
メロンパン: 12個, あんぱん: 7個
という文字列が作られ、
AIが売上データを読める形へつながっていきます。
🐹「Pythonの書き方を勉強してたら、最後はちゃんと画面のAIにつながった。」
📝 6時限目まとめ
今回は、app.py の _generate_ai_advice() にある実コードから、
f文字列
文字列の中へ変数などの値を埋め込む。
f"{name}: {qty}個"
アンパック
まとまった複数の値を、それぞれの変数へ取り出す。
for name, qty in ranked_sales
リスト内包表記
繰り返しながら、新しいリストを作る書き方。
[f"{name}: {qty}個" for name, qty in ranked_sales]
join
複数の文字列を1つにつなげる。
", ".join(...)
を学び直しました。
実コードでは、
ranked_sales
↓
nameとqtyへアンパック
↓
f文字列を作る
↓
リスト内包表記でリストにする
↓
joinで1つにつなげる
↓
sales_summaryへ代入する
と、1本につながっています。
🤔 自分で確認してみる
最後に、今回の内容を自分でも確認してみます。
Q1
次のコードの先頭にある f は何のためにあるでしょう?
f"{name}: {qty}個"
A. for文を開始する
B. 文字列の中へ値を埋め込む
C. 関数を定義する
🅰️答えを見る
B. 文字列の中へ値を埋め込む
です。
例えば、
name = "メロンパン"
qty = 12
なら、
メロンパン: 12個
になります。
Q2
次のコードでは、
for name, qty in ranked_sales
なぜ変数が2つあるのでしょう?
🅰️答えを見る
今回の ranked_sales では、
("メロンパン", 12)
のように、商品名と販売数が2つで1組になっています。
それを、
name
qty
へそれぞれ取り出しているためです。
Q3
次のコードは何を作るでしょう?
[f"{name}: {qty}個" for name, qty in ranked_sales]
ranked_sales は、
[
("メロンパン", 12),
("あんぱん", 7),
]
とします。
🅰️答えを見る
[
"メロンパン: 12個",
"あんぱん: 7個",
]
という新しいリストを作ります。
Q4
次の、
", ".join(...)
の、
", "
は何を表しているでしょう?
🅰️答えを見る
文字列同士をつなぐときに使う、
区切り文字
です。
今回なら、
メロンパン: 12個, あんぱん: 7個
のように、
カンマ + 半角スペース
が間に入ります。
Q5
次の実コード全体では、最後にどのような値ができますか?
sales_summary = ", ".join(
[f"{name}: {qty}個" for name, qty in ranked_sales]
)
ranked_sales は、
[
("メロンパン", 12),
("あんぱん", 7),
]
とします。
🅰️答えを見る
メロンパン: 12個, あんぱん: 7個
という1つの文字列が作られ、
sales_summary
へ入ります。
Q6
次の2つは、今回の範囲ではどんな関係でしょう?
sales_items = []
for name, qty in ranked_sales:
sales_items.append(f"{name}: {qty}個")
そして、
[f"{name}: {qty}個" for name, qty in ranked_sales]
🅰️答えを見る
どちらも、
ranked_salesから
nameとqtyを取り出す
↓
f文字列を作る
↓
新しいリストを作る
という処理です。
下のコードは、
リスト内包表記
を使って短く書いています。
Q7
今回の処理で、
qty = 12
のような整数が含まれているのに、
", ".join(...)
を使えるのはなぜでしょう?
🅰️答えを見る
先に、
f"{name}: {qty}個"
というf文字列を作っているからです。
qty = 12
↓
f文字列の中へ入る
↓
"メロンパン: 12個"
↓
文字列になる
↓
joinでつなげられる
という流れです。
🐹 6時限目を終えて
今回は、
sales_summary = ", ".join(
[f"{name}: {qty}個" for name, qty in ranked_sales]
)
という、たった1つの実コードを教材にしました。
最初に見ると、
🐹「カッコの中にforがいて、その外にjoinまでいる。」
となります。
でも分解すると、
f文字列
↓
値を文字列へ入れる
アンパック
↓
2つの値をnameとqtyへ分ける
リスト内包表記
↓
繰り返しながらリストを作る
join
↓
複数の文字列を1つにつなげる
という、それぞれの役割を見ることができました。
さらに今回は、その1行だけではなく、
ranked_sales = sorted(
sales_data.items(),
key=lambda item: item[1],
reverse=True,
)
まで少しだけ前へ戻りました。
すると、
sales_data
↓
辞書のitems()から
商品名と販売数を取り出す
↓
ranked_sales
↓
nameとqtyへアンパック
という流れまでつながりました。
そして今回も、
2時限目で学んだ、
for
や、
1時限目から何度も使っている、
=
による代入が出てきました。
さらに完成した、
sales_summary
は、
3時限目で見た、
build_sales_prompt(sales_summary)
へつながっています。
以前学んだコード
↓
もう一度見る
↓
前は気にしなかった書き方を見つける
↓
さらに分解する
↓
前の基礎ともつながる
今回もこの繰り返しです。
🐹「1行だけでも、掘る場所は結構ある。」
もちろん今回も、記事を1本書いただけで、
「リスト内包表記を完全に理解した!」
とは考えません。
これからコードを読んでいて、
f"..."
を見つけたら、
何の値を文字列へ入れているんだろう?
と考える。
for a, b in ...
を見つけたら、
何を2つに分けているんだろう?
と考える。
[... for ...]
を見つけたら、
何を繰り返して、
どんなリストを作っているんだろう?
と考える。
.join(...)
を見つけたら、
何を、どんな区切りでつないでいるんだろう?
と考える。
そんな読み方が少しずつできれば、
以前なら、
sales_summary = ", ".join(
[f"{name}: {qty}個" for name, qty in ranked_sales]
)
を、
「なんか難しそうな1行」
として通り過ぎていたところも、
少しずつ中身が見えるようになりそうです。
そして今回、途中で出てきた、
("メロンパン", 12)
というタプルも、まだ詳しく学んでいません。
さらに、
key=lambda item: item[1]
というlambdaも出てきました。
🐹「また次に調べられそうなものが増えた。」
全部を一度に覚えようとせず、
必要になったところから一つずつ掘っていきます。
まだまだPython基礎の学び直しは続きます。
次の時限も、🍞 sales_data_app の実コードを教材にして読んでいきます🐹
📚️ 基礎学習シリーズ
🏫1時限目
「変数・データ型・比較演算子・if文とは?」
🏫2時限目
「リスト・辞書・set・for文とは?」
🏫3時限目
「関数・引数・return・関数の呼び出し・型ヒントとは?」
🏫4時限目
「モジュール・import・例外・try・exceptとは?」
🏫5時限目
「クラス・インスタンス・init・self・属性とは?」
🏫6時限目
「f文字列・アンパック・リスト内包表記・joinとは?」
🏫7時限目
「strip・len・splitlines・スライスとは?」
🏫8時限目
「None・is・is not・isinstanceとは?」
