見出し画像

【AI文芸 玲奈様のプログラミング講座】Pythonの型非明示によるバグ

多分今回のは実用的な記事だと思います。


Part 1 概論

「Pythonの型非明示によるバグについて、詳細に語れって? いいわ、付き合ってあげる」

玲奈は、ゆっくりと指を組みながら話し始める。

「Pythonは動的型付け言語ね。つまり、変数の型を明示しなくても動くわけ。でも、それが原因で地雷を踏むことになるのよ。たとえば、こういうコードがあったとする」

def add(a, b):
    return a + b

print(add(2, 3))   # 5
print(add("2", "3"))  # "23"
print(add(2, "3"))  # TypeError

「見ての通り、add(2, 3) は整数同士だから問題なし、add("2", "3") も文字列の結合として動く。でも add(2, "3") でクラッシュ。だって、Pythonは引数の型を決め打ちしてないから、どんな値が入ってくるか分からない。型ヒントを使っても、あくまでヒントでしかなく、静的な型チェックがない限り実行時エラーは防げない」

玲奈は一度間を置いて、スマホを弄るふりをしながら続ける。

「こういうのは mypy みたいなツールを使えば事前に検出できるけど、実際のプロジェクトでは 'とりあえず動けばいい' って書かれることが多いのが現実よね。だから、こんなバグもよく見るわ」

def process_data(data):
    return data.strip()  # 文字列の前後の空白を削除

print(process_data("  hello  "))  # "hello"
print(process_data(None))  # AttributeError: 'NoneType' object has no attribute 'strip'

「None を渡されたら即死。静的型付けの言語ならコンパイル時に気づけるけど、Pythonだと実行するまで分からない」

玲奈は微かに笑いながら、「でも、これが一番ヤバいわね」と言いながら、次のコードを書いた。

def multiply_by_two(value):
    return value * 2

print(multiply_by_two(5))   # 10
print(multiply_by_two("5"))  # "55" (文字列の繰り返し)

「このバグの厄介なところは、エラーにならないこと。期待と違う動作をしているのに、クラッシュしないから、気づかないまま進行するのよ。しかも、Pythonのコードはこういう型の曖昧さが積み重なってるから、気づいた頃には大規模なデバッグが必要になる」

玲奈は腕を組み、淡々と言う。

「結論? 型を信用しないこと。型ヒントをつける、mypy を導入する、テストを書く、どれも大事。でも、最も重要なのは 'Pythonは型が自由なぶん、型のミスが発生しやすい' って意識することよ。油断したら、きっと痛い目を見るわ」


Part 2 JSONのパース

「勘違いじゃないわ。Pythonの型非明示のせいで、JSONのパース時に地雷を踏むのは、かなりよくあることよ」

玲奈はスマホを取り出し、手早くメモを開きながら話し始める。


1. JSONはすべてが"文字列"として扱われがち

「まず前提として、JSONはテキストデータだから、元の型情報は保持してないの。数値も文字列も、JSONとしてはただのテキスト。だから、Pythonに読み込ませたときの解釈次第でバグる

import json

data = '{"age": "25"}'  # ageが文字列になっている
parsed = json.loads(data)
print(parsed["age"] + 5)  # TypeError: can only concatenate str (not "int") to str

「APIのレスポンスなんかだと、"25" のように数値が文字列として入っていることがよくあるの。でも、Pythonは型推論をしないから、そのまま str として処理される。数値計算しようとすると、エラーになるのよ」


2. None と null の誤解

「JSONの null はPythonの None に変換されるんだけど、これは別の型問題を引き起こす」

data = '{"value": null}'
parsed = json.loads(data)

if parsed["value"] == 0:
    print("ゼロと同じ?")

「実行しても何も表示されない。だって、Pythonでは None と 0 は別物。でも、他の言語やデータベースでは null == 0 みたいな扱いをするものもあるから、ここでバグる可能性がある」


3. True / False の変換ミス

「JSONの true / false は、Pythonの True / False にちゃんと変換される。でも、問題は文字列として入ってる場合

data = '{"is_active": "false"}'  # 本当は boolean のつもり
parsed = json.loads(data)

if parsed["is_active"]:
    print("有効")  # 実行される(!)

「このバグのヤバいところは、Pythonでは 空でない文字列は True になること。だから、本当は False のつもりなのに、if "false" は True と判定される。ブール値を文字列として扱っていると、真逆の動作をする危険がある」


4. 数値の型変換 (int vs float)

「JSONでは 1 も 1.0 も数値としては一緒に見えるけど、Python側では int と float の区別がある」

data = '{"score": 95.0}'
parsed = json.loads(data)

if parsed["score"] == 95:
    print("一致")  # 実行される
print(type(parsed["score"]))  # <class 'float'>

「このコードは if parsed["score"] == 95 で True になるから、一見問題ないように見えるわね。でも、型をチェックすると float になってる。例えば、辞書のキーに使おうとすると、意図しない挙動になる」

scores = {95: "A", 96: "B"}
print(scores.get(parsed["score"]))  # None になる

「parsed["score"] は 95.0(float)だから、int の 95 とは別物として扱われるの。つまり、辞書のキーとして一致しない」


5. Decimal との相性

「Pythonには decimal.Decimal っていう高精度の数値型があるんだけど、JSONをパースすると float になってしまって、精度が失われることがある」

from decimal import Decimal
import json

data = '{"price": 19.99}'
parsed = json.loads(data)

price = Decimal(parsed["price"])  # 精度が落ちる可能性あり
print(price)  # 19.990000000000001 になることがある

「これは float の丸め誤差の問題ね。金融系の処理では Decimal を使うべきだけど、JSONを経由すると float に変換されるから、誤差が入り込むリスクがある」


結論

玲奈はコーヒーを一口飲み、静かに話を締めくくる。

「JSONのパースは単純そうに見えるけど、Pythonの型の曖昧さのせいで思ってたのと違うデータ型になることがよくある。対策? ちゃんとやるなら、こういう方法があるわ」

対策

  1. 型を明示的に変換する parsed["age"] = int(parsed["age"]) # 文字列だったら `int` にする

  2. None を意識する if parsed["value"] is None: print("デフォルト値をセット")

  3. ブール値を str で扱わない if str(parsed["is_active"]).lower() == "true": is_active = True else: is_active = False

  4. float / int の型チェック if isinstance(parsed["score"], float): parsed["score"] = int(parsed["score"])

  5. json.loads(data, parse_float=Decimal) を使う import json from decimal import Decimal data = '{"price": 19.99}' parsed = json.loads(data, parse_float=Decimal) print(parsed["price"]) # Decimal('19.99')

「Pythonは便利だけど、こういう型のゆらぎに気をつけないと、静かにバグが潜むのよ。ま、慎重にやりなさいってことね」

いいなと思ったら応援しよう!