CHAPTER 3 ・ エラーに強いコード / Lesson 11

原因を探す(デバッグの手順)

結果がおかしいとき、闇雲に直さない。途中の値を見て、範囲を狭めて、原因を特定する手順です。

目安 20分 ・ インストール不要

このレッスンが終わったら

「おかしいときは、途中の値を print で確かめ、範囲を半分ずつ狭める。直す前に、原因を言葉にする」と言える。

STEP 1

こんな場面、ありませんか?

営業部の売上を合計するコードを書きました。実行すると、エラーも出ず、数字が表示されます。ところが、電卓で足した 1,110,000円 と、合いません。

営業部の売上を合計するコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

730000 と表示されました。3行目も「営業」に見えるのに、足されていません。エラーが出ないバグは、赤い表示というヒントがないぶん、探しにくいものです。

こんなとき、勘で書き換えるのは、遠回りです。この回では、原因を手順どおりに探して、直す方法を学びます。コードの間違いや不具合を直す作業を、デバッグと呼びます。

STEP 2

手順にそって、原因を探す

まず、どこまでが合っていて、どこからおかしいかを調べます。行ごとに、部署の値と、「営業」と等しいかを表示します。

途中の値を表示して、範囲を狭めるコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

3行目だけが False です。足し算ではなく、「営業と等しいか」の比べ方の問題だと分かりました。次に、その部署の値を、見えない部分まで確かめます。repr()(値を、プログラムに書く形で表示する)と len()(文字数)を使います。

repr と len で、中身を確かめるコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

'営業 ' と表示され、文字数は3です。  は全角の空白を表す記号で、見た目には現れず、「営業」の後ろに紛れ込んでいたのです。仮説は「全角の空白があるせいで、一致しない」。strip() で空白を除けば、合うはずです。直して、確かめます。

直して、期待した値になるか確かめるコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

1110000 になりました。strip() は、全角の空白も取り除きます。

STEP 3

仕組みを読む

いまの流れを、5つの手順にまとめます。おかしいとき、この順に進めます。

  1. ① 再現する同じ結果を出せる最小のデータで
  2. ② 範囲を狭めるどこまでが正しいか
  3. ③ 値を見るprint / repr / type
  4. ④ 仮説を立てる原因を言葉にする
  5. ⑤ 直して確認期待した値になったか
④の「原因を言葉にする」までは、コードを直しません。直すのは⑤です。

エラーが出るときは、トレースバック(エラーまでの道のり)が手がかりになります。基礎編では「最後の2行」を読みました。今回は、道のりの全体を読みます。わざとエラーを出してみましょう。

関数の中で起きるエラーコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

結果の下の「エラーの全文を見る」を押すと、次のような表示が出ます(行番号以外は、サイトの表示と少し違うことがあります)。

Traceback (most recent call last):
  File "<exec>", line 11, in <module>
  File "<exec>", line 7, in total_of
  File "<exec>", line 2, in read_amount
ValueError: invalid literal for int() with base 10: '3,500'
トレースバックを、上から1行ずつ日本語にする
Traceback (most recent call last):ここから、エラーまでの道のりが始まる
File "<exec>", line 11, in <module>11行目で total_of を呼んだ(いちばん外側)
File "<exec>", line 7, in total_ofその中の7行目で read_amount を呼んだ
File "<exec>", line 2, in read_amountさらにその中の2行目で、エラーが起きた(現場)
ValueError: invalid literal ... '3,500'エラーの種類と理由。'3,500' は int にできない

読み方は、下から上へです。いちばん下の行で「何が起きたか」を確かめ、その上の行へたどって、自分のコードのどの行かを探します。全文には、サイトの仕組みの行(_pyodide と書かれた行)が混ざることがありますが、<exec> と書かれた行が、あなたのコードです。

STEP 4

変えて、試す

① 範囲を半分ずつ狭める。 処理が何段階もあるとき、真ん中あたりで値を見ます。そこが正しければ、原因は後半に。おかしければ、前半にあります。これを繰り返すと、少ない回数で絞り込めます。金額の下ごしらえで、合計が 5,900 のはずが 4,700 になるコードです。

各段階の値を表示して、ずれる場所を探すコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

1: では4件、2: では2件。1と2のあいだで、"800" と "400" が消えています。len(s) > 3 は、空欄を除くつもりが、3文字以下の金額まで除いていました。if s != "" に直せば、5900になります。

② 「コピーしたつもり」のリスト。 リストを別の変数に入れても、コピーにはなりません。

元のリストまで変わってしまうコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

with_tax = prices は、同じリストに、2つ目の名前を付けただけです。元のリストを残したいときは、with_tax = list(prices) のようにコピーを作ります。

STEP 5

自分で書く

どのお題も、そのまま実行すると、正しくない結果になります。原因を言葉にしてから、直してください。

お題① CSVから読んだ金額の中で、いちばん大きい金額を出したい。正しくは 120000 ですが、95000 と出ます。

お題①:いちばん大きい金額(文字と数字)コードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

print(type(amounts[0])) で、値の型を確かめてみましょう。文字の大小は、数字の大小とは違う順番で決まります。int() で数字に直してから比べます。

答えの例を見る
amounts = ["95000", "120000", "8000"]

print(max(int(a) for a in amounts))

お題② 月ごとの売上の「前の月との差」を出したい。ところが、エラーになります。エラーの最後の2行を読んで、直してください。

お題②:前の月との差(1つずれ)コードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

i が 3 のとき、sales[i + 1] は存在しません。差は、4つの値のあいだに3個だけあります。range の終わりを1つ減らします。

答えの例を見る
sales = [100000, 105000, 98000, 106000]

diffs = []
for i in range(len(sales) - 1):
  diffs.append(sales[i + 1] - sales[i])
print(diffs)

お題③ 税抜の価格を、税込(10%を加え、小数点以下は切り捨て)にする関数です。2つの合計が 3300 になるはずが、エラーになります。

お題③:税込にする関数(None)コードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

エラーの最後の行に出てくる NoneType は、「値がない」という意味です。with_tax(1000) を print して、何が返ってきているか見てみましょう。

答えの例を見る
def with_tax(price):
  return price * 11 // 10

total = with_tax(1000) + with_tax(2000)
print(total)
STEP 6

仕事ならどう使う?

AIに渡す文章

次のPythonコードの結果が、期待と違います。原因を、まず言葉で説明してください。すぐに直したコードを出さず、確認するための print の置き場所も教えてください。【コード】(ここに貼る)【入力の例】(数行)【期待した結果】【実際の結果】【エラーの全文(あれば)】

理解チェックエラーは出ないのに、合計が合いません。最初にすることは、どれでしょう?

理解チェックトレースバック(エラーの全文)の読み方として、正しいのはどれでしょう?