Skip to content
This page has been auto-translated and may contain errors.View in English

バグハウス

おめでとうございます。あなたは Crooked Orbit の技術責任者です。怪しい宇宙飛行士たちが運営している、ちょっと混沌とした小さな宇宙物流会社で、彼らが書くコードはさらに怪しいものばかり。

給料はまあまあ。でもコードベースはそうでもない…

あなたの仕事は、バグ、壊れた関数、技術的には動くけど本当は動くべきじゃないものなど、問題を一つずつ片付けていくことです。

#1: 名前フォーマッター

Pip ねえ、名前フォーマッターを書いたんだけど、なんか…何もしてくれないんだよ? 😭 エラーは出ないで普通に動くんだけど、名前が全部小文字のまま。ループの中で各要素に .capitalize() を呼んでるのが見えるんだけど。意味わかんない。ちょっと見てくれない?
python
def format_name(full_name):
    parts = full_name.split()
    for part in parts:
        part = part.capitalize()
    return " ".join(parts)

print(format_name("alice van den berg"))

期待される結果: Alice Van Den Berg

実際の結果: alice van den berg

何が問題で、どう直しますか?

ヒント

for ループの中で変数を再代入したとき、実際には何が起こるか考えてみてください。part を変更すると、それが元のリストに影響しますか?

修正方法の一例

for part in parts と書くと、Python はリストから一度に一つずつ要素を取り出してくれます。part は現在の要素を保持している一時的な変数にすぎません。リストそのものへの直接的なつながりではありません。

つまり part = part.capitalize() と書くと、part が指している先を置き換えているだけです。parts には元の文字列がそのまま入っています。実際にはリストを変更していないのです。

これをはっきりと見るには:

python
parts = ["alice", "van", "den", "berg"]

for part in parts:
    part = part.capitalize()

print(parts)
# ['alice', 'van', 'den', 'berg']  # 何も変わっていない

修正するには、大文字化された値で新しいリストを作る必要があります。リスト内包表記がこれをきれいに行えます:

python
def format_name(full_name):
    parts = full_name.split()
    return " ".join(part.capitalize() for part in parts)

print(format_name("alice van den berg"))
# Alice Van Den Berg

part.capitalize() for part in parts は各名前を順に処理して、大文字化し、結果を新しいリストに集めます。そして .join() でそれらをスペースで繋ぎ直します。

#2: トップスコア

Zee ちょっと恥ずかしいんだけど、リストの中で一番高いスコアを見つけるはずの関数を書いたら、ただ None って出力されるだけなの。毎回。もう5回くらいループを見直したけど、ロジックは大丈夫そうに見えるんだよね。スコアはちゃんと入ってるし。何が起こってるのかわからない 😅
python
def highest_score(scores):
    best = 0
    for score in scores:
        if score > best:
            best = score

results = [45, 92, 78, 88, 65]
print(f"Top score: {highest_score(results)}")

期待される結果: Top score: 92

実際の結果: Top score: None

何が問題で、どう直しますか?

ヒント

return 文を書かなかった場合、関数は何を返しますか?

修正方法の一例

ループは問題ありません。ロジックも問題ありません。問題は、highest_score が実際には何も返していないことです。最高スコアを見つけて、そのまま何もしません。

Python では、関数に return 文がない場合、自動的に None を返します。それが出力されているものです。

修正は最後に1行追加するだけ:

python
def highest_score(scores):
    best = 0
    for score in scores:
        if score > best:
            best = score
    return best

results = [45, 92, 78, 88, 65]
print(f"Top score: {highest_score(results)}")
# Top score: 92

これはよくあることです。ロジックは全部そこにあるので関数は完成しているように見えますが、return がないと、関数が終わったときに結果が消えてしまいます。

#3: 予算チェック

Orla 小さな予算トラッカーを作ってるんだけど、数字を入力するたびにクラッシュするの。何もしないうちに、いきなり TypeError で落ちる。ロジックは確認したけど大丈夫そうに見えるんだよね。閾値は float で、入力は数字…何に文句を言ってるんだろう…助けてー
python
def check_budget(threshold):
    spent = input("How much have you spent? ")
    if spent >= threshold:
        print("Over budget!")
    else:
        print("You're within budget.")

check_budget(500.0)

エラー:

TypeError: '>=' not supported between instances of 'str' and 'float'

何が問題で、どう直しますか?

ヒント

ユーザーが何を入力しても、input() は常にどんな型を返しますか?

修正方法の一例

input() は常に文字列を返します。ユーザーが数字を入力したかどうかは関係ありません。Python にはそれがわかりません。入力されたものをそのままテキストとして返すだけです。

なので spent >= threshold を比較しようとすると、文字列と float を比較していることになり、Python はそれを拒否します。これが TypeError の正体です。

修正は、比較する前に入力を数値に変換することです。小数を扱うために float() を使います:

python
def check_budget(threshold):
    spent = float(input("How much have you spent? "))
    if spent >= threshold:
        print("Over budget!")
    else:
        print("You're within budget.")

check_budget(500.0)
# How much have you spent? 620
# Over budget!

input()float() で包むと、すぐに文字列を数値に変換するので、比較が期待通りに動きます。

これは Python でよくある混乱の原因の一つです: input() は数字を読み取っているように見えますが、常に文字列を返します。自分で変換する必要があります。

#4: 貨物ラベル

Dex マニフェスト用の貨物ラベルを生成してるんだけど、特定の行で毎回クラッシュするんだよね。アイテム名は文字列、重さは数字、大丈夫そうに見えるけど? Python が連結についてキレてる 😩
python
def cargo_label(item, weight):
    label = "Item: " + item + " | Weight: " + weight + "kg"
    return label

print(cargo_label("Moon rocks", 42))

エラー:

TypeError: can only concatenate str (not "int") to str

何が問題で、どう直しますか?

ヒント

Python の + 演算子は数値を自動的に文字列に変換してくれません。結合する前に何をする必要がありますか?

修正方法の一例

Python で + を使って文字列を結合するときは、すべての値が文字列でなければなりません。weight は整数なので、Python はそれを周囲のテキストにどう繋げればよいかわかりません。推測せずに拒否します。

修正は、weightstr() で文字列に変換することです:

python
def cargo_label(item, weight):
    label = "Item: " + item + " | Weight: " + str(weight) + "kg"
    return label

print(cargo_label("Moon rocks", 42))
# Item: Moon rocks | Weight: 42kg

f-string を使うと、こういう処理は変換を自動的に行ってくれるので、しばしばより簡潔になります:

python
def cargo_label(item, weight):
    return f"Item: {item} | Weight: {weight}kg"

print(cargo_label("Moon rocks", 42))
# Item: Moon rocks | Weight: 42kg

{} の中では、Python が自動的に値を文字列表現に変換するので、str() は不要です。

#5: 乗客名簿

Cass すごく変なことが起きてるんだよね。各フライトの乗客リストを作る関数があって、最初のフライトはちゃんと動いてるように見える。でも3回目になると、前のフライト全員も名簿に入ってる? 呼び出し間でデータを共有してないし、それぞれ完全に別々のはずなんだけど。本当に何が起こってるのかわからない 😰
python
def build_manifest(name, passengers=[]):
    passengers.append(name)
    return passengers

flight_1 = build_manifest("Pip")
flight_2 = build_manifest("Zee")
flight_3 = build_manifest("Orla")

print(flight_1)
print(flight_2)
print(flight_3)

期待される結果:

['Pip']
['Zee']
['Orla']

実際の結果:

['Pip']
['Pip', 'Zee']
['Pip', 'Zee', 'Orla']

何が問題で、どう直しますか?

ヒント

Python のデフォルト引数の値は、関数が定義されたときに一度だけ評価されます。関数が呼び出されるたびにではありません。リストのようなミュータブルなオブジェクトにとって、これは何を意味するでしょうか?

修正方法の一例

これは Python の中でも最もよく知られている驚きの一つです。passengers=[] をデフォルト引数として書くと、Python は関数が定義されたときにそのリストを一度だけ作成します。デフォルトを使うすべての呼び出しが、まったく同じリストオブジェクトを共有しています。なので、ある呼び出しでそれに追加すると、その後のすべての呼び出しに影響します。

標準的な修正方法は、デフォルトとして None を使い、関数の中で新しいリストを作ることです:

python
def build_manifest(name, passengers=None):
    if passengers is None:
        passengers = []
    passengers.append(name)
    return passengers

flight_1 = build_manifest("Pip")
flight_2 = build_manifest("Zee")
flight_3 = build_manifest("Orla")

print(flight_1)  # ['Pip']
print(flight_2)  # ['Zee']
print(flight_3)  # ['Orla']

これでリストを渡さない呼び出しは、それぞれ真新しいリストを得るようになります。

これはあらゆるミュータブルなデフォルトに当てはまります: リスト、辞書、集合。毎回新しいものが欲しいなら、シグネチャに入れないでください。None を使って、関数の内側で作りましょう。