1-01 値・型・変数

このページはプロモーション(広告)を含みます。

プログラムが扱う「値」と、その種類である「型」、値に名前を付ける「変数」の仕組みを、金額計算の落とし穴まで含めて学びます。

この章でできるようになること

ここから Python の本格的な学習に入ります。最初の章で扱うのは、プログラムの一番小さな部品である「値」と、その種類を表す「型」、そして値に名前を付ける「変数」です。地味に見えますが、AIが書いたコードを読むとき、最初に確かめるのは「この名前には何が入っているのか」「それは数なのか文字なのか」です。ここがあいまいなままだと、後の章のどこかで必ずつまずきます。

実務でも、この章の内容はそのまま事故につながります。たとえば、CSVから読んだ売上が文字列のままで合計できない。税込価格を小数で計算して1円ずれる。割引後の金額を切り捨てたつもりが、なぜか1円少なくなる。これらはすべて「値と型」の理解で防げます。この章では、なぜそうなるのかという仕組みまで踏み込みます。

前の章(→ 0-01 AIエンジニアという仕事)では print で文字を表示しました。今回はその print の中に書いていた「もの」の正体を明らかにします。章の最後には、LLMアプリで値と型がどう現れるか(モデルの返答は常に文字列である、など)もつなげます。

この章の進め方

python predict と書かれたコードは、実行する前に出力を紙かメモに予想してください。予想が外れたところが、まさに理解が足りない場所です。外れるのは失敗ではなく、学習が起きている合図です。

式と値:Pythonは「計算して値を作る」

まずは動かしてみます。次のコードは、ある商品の売上を計算して表示します。

アプリで実行できるコード

print(1980 * 3)
print("ノートPCスタンド")
print(1980 * 3 + 500)

1980 * 3 のように、計算して何かを生み出す書き方を式(expression)と呼びます。式を計算した結果が値(value)です。1980 * 3 という式は 5940 という値になります。"ノートPCスタンド" も式の一種で、計算するまでもなく、それ自身が値です。

Python がしていることは、突き詰めると「式を計算して値を作り、その値を使って次の計算をする」ことの繰り返しです。1980 * 3 + 500 は、先に 1980 * 3 を計算して 5940 を作り、次に 5940 + 500 を計算して 6440 を作ります。算数と同じく、掛け算・割り算は足し算・引き算より先に計算されます。順番を変えたいときは丸かっこ () を使います。

実行前に出力を予想する

print(1980 * (3 + 500))
print((1980 + 20) * 3)
print(10 - 4 - 3)

最初の行は丸かっこの中の 3 + 500 が先なので、1980 * 503 です。最後の 10 - 4 - 3 は左から順に計算され、(10 - 4) - 3 で 3 になります。10 - (4 - 3) の 9 ではありません。同じ優先度の演算は左から順に計算する、という規則があるからです。

このアプリと Jupyter の違い

Google Colab などのノートブックでは、セルの最後に式だけを書くと値が自動で表示されます。このアプリのコード実行では、最後の式は表示されません。値を見たいときは必ず print() で囲んでください。

print は値を画面に出す道具です。値そのものではありません。この区別は → 1-07 関数 で「return と print の違い」として改めて重要になります。今の段階では「計算は式がする、表示は print がする」と覚えておけば十分です。

値には型がある

値には種類があります。この種類を型(type)と呼びます。Python で最初に覚える型は5つです。

型の名前 読み方 例 何を表すか
int イント(整数) 1980, 0, -300 小数点のない数。個数、円単位の金額、件数
float フロート(浮動小数点数) 0.1, 3.14, 2.0 小数点を含む数。比率、平均、温度
str ストリング(文字列) "東京", "1980", "" 文字の並び。名前、住所、問い合わせ文
bool ブール(真偽値) True, False はい/いいえ。条件を満たすか
NoneType ノン None 「値がない」ことを表す特別な値

値の型は type() で確かめられます。予想してから実行してください。

実行前に出力を予想する

print(type(1980))
print(type(1980.0))
print(type("1980"))
print(type(True))
print(type(None))

1980 と 1980.0 と "1980" は、人間の目には同じ「1980」に見えます。しかし Python にとっては、整数・浮動小数点数・文字列という、まったく別の種類の値です。表示の <class 'int'> の class は「型」とほぼ同じ意味だと思ってかまいません(→ 1-12 クラスとオブジェクト で正体が分かります)。

なぜ型があるのか

型があるのは、型によって「できること」が違うからです。数なら足し算ができます。文字列なら、文字をつなげたり、一部を取り出したりできます。同じ + という記号でも、型によって意味が変わります。

実行前に出力を予想する

print(1980 + 20)
print("1980" + "20")

数どうしの + は足し算、文字列どうしの + は連結(つなげること)です。だから "1980" + "20" は "198020" という6文字の文字列になります。これは「CSVから読んだ金額を足したら、とんでもなく大きな数になった」という実務の事故の正体です。CSVやフォームから読んだ値は、見た目が数字でも文字列であることが多いのです。

では、数と文字列を足すとどうなるでしょうか。

print("合計: " + 5940)
TypeError: can only concatenate str (not "int") to str

型エラー(TypeError)が起きます。メッセージは「文字列には文字列しか連結できません(int は連結できません)」という意味です。Python は「たぶん文字にしたいのだろう」と勝手に推測しません。推測しないおかげで、間違いがその場で表に出ます。直し方は後の「型変換」の節で扱います。

「3」と 3 は別物

症状:数を足したはずなのに "35" のような文字がつながった結果になる。あるいは TypeError が出る。

原因:見た目が数字の文字列("3")と、本物の整数(3)を混同している。CSV、Excel からのコピー、Webフォーム、LLMの返答は、基本的にすべて文字列で届きます。

対処:値が怪しいときは print(type(値)) で型を確かめる。計算の前に int() や float() で数に変換する。表示するときに型を見分けたいなら print(repr(値)) を使うと、文字列には引用符が付いて区別できます。

AIのコードで見かける isinstance

型を確かめる方法として、AIが書いたコードでは type() より isinstance() のほうをよく見かけます。isinstance(値, 型) は「その値がその型か」を True / False で答えます。

アプリで実行できるコード

print(isinstance(1980, int))
print(isinstance("1980", int))
print(isinstance(1980.0, (int, float)))

3行目のように、型を丸かっこで並べると「どれかに当てはまるか」を調べられます。isinstance は、受け取った値が想定どおりの型かを確かめる「入口の検査」でよく使われます。読むときは「ここで型を確かめて、違ったら別の処理をしようとしているのだな」と理解できれば十分です。条件によって処理を分ける書き方は → 1-05 条件分岐 で学びます。

None は「まだない」を表す

None は「値がない」ことを表す特別な値です。0 でも空の文字列 "" でもありません。たとえば顧客データで「電話番号が未登録」を表すとき、0 を入れると「0番という番号」と区別できません。"" を入れると「空欄で登録された」のか「まだ聞いていない」のかが曖昧になります。None は「そもそも値が存在しない」ことをはっきり示します。

アプリで実行できるコード

phone = None
email = ""
points = 0
print(repr(phone), repr(email), repr(points))
print(type(phone), type(email), type(points))

LLM の API でも None はよく出てきます。モデルが文章ではなくツールの呼び出しを返したとき、本文(content)が None になる、という形です(→ 6-04 ツール呼び出し)。None の扱い方は → 1-05 条件分岐 で詳しく学びます。

変数は「名前札」

計算した値を後で使いたいとき、値に名前を付けます。これが変数(variable)です。名前を付けることを代入(assignment)と呼び、= で書きます。

アプリで実行できるコード

unit_price = 1980
quantity = 3
subtotal = unit_price * quantity
print(subtotal)

unit_price = 1980 は「1980 という値に unit_price という名前を付ける」という意味です。数学の「等しい」ではありません。Python の = は「右側を計算して、その値に左側の名前を付ける」という命令です。右から左へ読むと理解しやすくなります。

箱ではなく、名前札

変数はよく「値を入れる箱」にたとえられます。最初のうちはそれでも困りません。しかし Python の実際の仕組みに近いのは、値に名前札を結びつけるというイメージです。

  名前札             値(メモリ上に作られたもの)
 [unit_price] ----> 1980
 [quantity]   ----> 3
 [subtotal]   ----> 5940

箱のたとえの限界は、「1つの値に2つの名前を付ける」ときに表れます。箱なら中身はどちらか一方にしか入りません。名前札なら、1つの値に何枚でも札を付けられます。

実行前に出力を予想する

price_a = 1980
price_b = price_a
price_a = 2200
print(price_a)
print(price_b)

順に追いかけます。price_b = price_a は「price_a が指している値(1980)に、price_b という札も付ける」という意味です。この瞬間、1980 には2枚の札が付いています。次の price_a = 2200 は「price_a の札を外して、新しく作った 2200 に付け替える」という意味です。price_b の札は 1980 に付いたまま動きません。だから price_b は 1980 のままです。

 price_b = price_a のあと        price_a = 2200 のあと
 [price_a] --+                    [price_a] ----> 2200
             +--> 1980            [price_b] ----> 1980
 [price_b] --+

「price_b = price_a で、2つの変数がつながった」と考えると、price_b も 2200 になると予想してしまいます。実際には、つながっているのは名前どうしではなく、名前と値です。この名前札のモデルは → 1-03 リストとタプル で、もっと重要になります。中身を書き換えられる値(リスト)に2枚の札が付いていると、片方の札から書き換えた変更がもう片方からも見える、という現象が起きるからです。

再代入と「自分自身を使った更新」

同じ名前に別の値を付け直すことを再代入と呼びます。よく使うのが、今の値をもとに新しい値を作る書き方です。

実行前に出力を予想する

order_count = 10
order_count = order_count + 1
order_count = order_count * 2
print(order_count)

order_count = order_count + 1 は、数学なら成り立たない式です。しかし Python では「右側を先に計算する」ので問題ありません。右側の order_count + 1 は、その時点の order_count(10)を使って 11 になります。その 11 に order_count の札を付け替えます。次の行では 11 * 2 で 22 になります。

この書き方は頻出なので、短く書く記法があります。order_count += 1 は order_count = order_count + 1 と同じ意味です。-=、*=、/= も同様です。AIが書くコードでは、件数を数えるときや合計を積み上げるときに必ず出てきます。

値を追いかける表を書く

変数が何度も付け替えられるコードは、頭の中だけで追うと必ずどこかで混乱します。そういうときは、紙やメモに「1行実行するごとに、各変数が何を指しているか」の表を書きます。これをトレース表と呼びます。

 行                              order_count
 order_count = 10                10
 order_count = order_count + 1   11
 order_count = order_count * 2   22

地道な方法ですが、プロのエンジニアも複雑なバグを追うときに同じことをします。AIが書いた数十行のコードで「どこで値がおかしくなったのか」を探すときも、怪しい行の前後に print(変数名, repr(変数)) を入れて、トレース表を実際の出力で作ります。予想した表と実際の出力が食い違った行が、バグの場所です(→ 3-06 AIと一緒にデバッグする)。

存在しない名前を使うと

札を付けていない名前を使うと、エラーになります。

total = subtotl * 1.1
NameError: name 'subtotl' is not defined. Did you mean: 'subtotal'?

名前エラー(NameError)は「その名前は定義されていない」という意味です。ほとんどの場合、原因はタイプミスか、代入する前に使っていることです。最近の Python は「もしかして subtotal のことですか」と候補まで示してくれます。エラーメッセージは敵ではなく、手がかりです。読み方は → 1-08 エラーを読み、直す で体系的に学びます。

名前札の正体を覗く(分からなくても先に進んでかまいません)

Python のすべての値は、メモリ上に作られた「オブジェクト」です。変数はオブジェクトへの参照(どこにあるかの情報)を持っています。2つの名前が同じオブジェクトを指しているかは is で確かめられます。

アプリで実行できるコード

a = 1980
b = a
print(a is b)
b = 2200
print(a is b)

最初の a is b は、2つの札が同じ値に付いているので True です。b を付け替えた後は別々の値を指すので False になります。is は「同じものか」、== は「値が等しいか」を調べます。数や文字列の比較には == を使い、is は None との比較(x is None)など特別な場面で使う、と覚えてください。小さな整数では、Python が内部で同じオブジェクトを使い回すことがあるため、is の結果が直感と違うことがあります。だからこそ数の比較に is を使ってはいけません。

算術演算:割り算は3種類ある

数の計算に使う記号(演算子)をまとめます。

演算子 意味 例 結果
+ 足し算 1980 + 20 2000
- 引き算 5000 - 1980 3020
* 掛け算 1980 * 3 5940
/ 割り算 10 / 4 2.5
// 割り算の商(切り捨て) 10 // 4 2
% 割り算の余り 10 % 4 2
** べき乗 2 ** 10 1024

注意が必要なのは / です。予想してください。

実行前に出力を予想する

print(10 / 2)
print(type(10 / 2))
print(10 // 2)
print(7 // 2, 7 % 2)

/ は、割り切れる場合でも結果が必ず float になります。10 / 2 は 5 ではなく 5.0 です。これは「割り算の結果は一般に小数になりうるので、型を1つに揃えておく」という Python 3 の設計です。整数の結果がほしいときは // を使います。

// と % の実務での使い道

//(商)と %(余り)は、業務の計算でよく組み合わせて使います。たとえば在庫を箱詰めする場面です。

アプリで実行できるコード

stock = 50          # 在庫数
per_box = 12        # 1箱に入る数
full_boxes = stock // per_box
leftover = stock % per_box
print("満杯の箱:", full_boxes, "箱")
print("箱に入らない端数:", leftover, "個")

# 全部を出荷するのに必要な箱の数(切り上げ)
boxes_needed = (stock + per_box - 1) // per_box
print("必要な箱:", boxes_needed)
print("ちょうど48個なら:", (48 + per_box - 1) // per_box)

では「全部を出荷するには何箱必要か」はどう計算するでしょうか。端数が1個でもあれば、もう1箱必要です。上のコードの後半が、Python で切り上げの割り算をするときの定番の書き方です。

stock + per_box - 1 は「端数が1個でもあれば次の倍数を超えるように、あらかじめ per_box - 1 を足しておく」という工夫です。50個なら (50 + 11) // 12 = 61 // 12 = 5 箱。48個なら (48 + 11) // 12 = 59 // 12 = 4 箱で、ぴったりのときに余分な箱を数えません。math.ceil(stock / per_box) という書き方もありますが、こちらは一度 float を経由するため、非常に大きな数では誤差の心配があります。整数だけで済む計算は整数だけで行うのが安全です。

% は「偶数か奇数か」(n % 2 == 0)、「10件ごとに区切る」(i % 10 == 0)、「曜日の計算」などにも使います。

型が混ざったとき

int と float を混ぜて計算すると、結果は float になります。

実行前に出力を予想する

print(1980 * 2)
print(1980 * 2.0)
print(2 ** 3, 2 ** 0.5)
print(3 * "ab")

1980 * 2.0 は 3960.0 です。情報を失わない方向(より広く表せる float)に揃える、という規則です。最後の 3 * "ab" は、文字列の「繰り返し」で "ababab" になります。区切り線を "-" * 30 で作る、といった使い方をします。

負の数の // と %

// は「小さい方へ切り捨てる(床関数)」ので、負の数では直感とずれます。-7 // 2 は -3 ではなく -4 です。そして a == (a // b) * b + a % b が常に成り立つように、-7 % 2 は 1 になります。返金や差額の計算で負の数を割るときは、この挙動に注意してください。0 の方向へ切り捨てたいなら int(-7 / 2) で -3 になりますが、float を経由する点に注意が必要です。

浮動小数点の誤差と、金額計算の鉄則

この章で最も重要な節です。次のコードの出力を予想してください。

実行前に出力を予想する

print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)

多くの人が 0.3 と True を予想します。実際には 0.30000000000000004 と False が表示されます。これは Python のバグではありません。JavaScript でも Java でも Excel の内部でも、ほぼすべてのコンピュータで同じことが起きます。

なぜ 0.1 を正確に表せないのか

仕組みを、身近な例から考えます。10進数で 1 / 3 を書くと 0.3333… と無限に続きます。どこかで打ち切れば、必ず少しずれます。10進数では、分母が 2 と 5 だけでできている分数(1/2、1/4、1/5、1/10 など)しか、有限の桁で書けません。

コンピュータは数を 2進数(0 と 1)で保存します。2進数で有限の桁で書けるのは、分母が 2 だけでできている分数(1/2、1/4、1/8 など)だけです。ところが 0.1 は 1/10 で、分母に 5 が含まれます。だから 2進数にすると 0.0001100110011… と無限に続き、どこかで打ち切るしかありません。

float は、およそ 10進数で15〜17桁分の精度で数を保存します(正確には 2進数で53桁)。0.1 と書いた瞬間、実際に保存されるのは「0.1 に最も近い、2進数で表せる数」です。小数点以下を20桁まで表示させると、ずれが見えます。

アプリで実行できるコード

print(format(0.1, ".20f"))
print(format(0.2, ".20f"))
print(format(0.3, ".20f"))

0.1 も 0.2 もわずかに大きめに保存されています。足すとずれが積み重なり、0.3 として保存される数(これもわずかにずれている)とは別の数になります。普段 print(0.1) が 0.1 と表示されるのは、Python が「保存された数に戻せる最短の表記」を選んで見せているからです。表示が 0.1 でも、中身は正確な 0.1 ではありません。

金額で起きる実際の事故

「小数点以下16桁目のずれなど、どうでもいい」と思うかもしれません。問題は、このずれが切り捨てと組み合わさったときに1円の差になることです。1,300円の商品を3割引にする計算をしてみます。

実行前に出力を予想する

price = 1300
discounted = int(price * 0.7)
print(price * 0.7)
print(discounted)

正しい答えは 910円です。しかし 1300 * 0.7 は 909.9999999999999 と、ほんのわずかに小さく計算されます。int() は小数点以下を切り捨てるので、結果は 909円になります。1件なら1円ですが、数万件の請求をまとめて処理すると、合計が帳簿と合わなくなります。そして、この種のバグはテストで偶然見つかりにくいのが厄介な点です。1290円、1300円、1310円……と特定の金額だけで起きるからです。

鉄則:金額は「整数の円」で計算する

日本円の金額は、整数(int)で扱うのが基本です。割合の計算は「掛けてから割る」順に、整数だけで行います。

アプリで実行できるコード

price = 1300
discounted = price * 70 // 100      # 3割引 = 70%
print(discounted)

unit_price = 1980
tax = unit_price * 10 // 100        # 消費税10%(1円未満切り捨て)
print(unit_price + tax)

price * 70 // 100 は、1300 * 70 = 91000 を先に整数で計算し、それを 100 で割った商を取ります。途中で float を一度も通らないので、誤差が入り込む余地がありません。

小数の計算がどうしても必要なとき(ドル建ての価格、利率、按分など)は、標準ライブラリの decimal(デシマル)を使います。Decimal は10進数のまま計算する型です。

アプリで実行できるコード

from decimal import Decimal, ROUND_HALF_UP, ROUND_DOWN

print(Decimal("0.1") + Decimal("0.2"))
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3"))

amount = Decimal("2178.5")
print(amount.quantize(Decimal("1"), rounding=ROUND_HALF_UP))   # 四捨五入
print(amount.quantize(Decimal("1"), rounding=ROUND_DOWN))      # 切り捨て

quantize(Decimal("1")) は「1の位まで(小数点以下0桁)に揃える」という意味で、rounding= で端数の処理方法を指定します。from decimal import ... は「decimal という道具箱から、これらの部品を取り出して使う」という意味です(→ 1-10 モジュールと標準ライブラリ)。

Decimal(0.1) と Decimal("0.1") は違う

症状:Decimal を使ったのに、誤差が消えない。

原因:Decimal(0.1) と書くと、先に 0.1 が float として作られ、ずれた値のまま Decimal に変換されます。print(Decimal(0.1)) を実行すると 0.1000000000000000055511151231257827021181583404541015625 と表示されます。

対処:Decimal には必ず文字列で数を渡します。Decimal("0.1") と書きます。AIが書いたコードで Decimal(0.1) や Decimal(price * 1.1) を見たら、その時点で誤差が入っていると読んでください。

round() は「四捨五入」ではない

もう1つ、よくある誤解を確かめます。

実行前に出力を予想する

print(round(0.5), round(1.5), round(2.5), round(3.5))

学校で習った四捨五入なら 1 2 3 4 です。実際は 0 2 2 4 になります。Python の round() は、ちょうど半分のときに偶数の側へ丸める「偶数丸め(銀行丸め)」を使います。大量のデータを丸めたときに、切り上げ側に偏らないようにするための方式です。さらに round(2.675, 2) は 2.68 ではなく 2.67 になります。2.675 という float が、実際には 2.67499999… として保存されているからです。

端数処理は「仕様」であり、プログラマが決めることではない

実務では、1円未満の端数を「切り捨て」「切り上げ」「四捨五入」のどれにするかは、会社のルールや取引先との契約で決まっています。消費税の端数処理も事業者が選べますが、インボイス制度(適格請求書)では「1枚の請求書につき、税率ごとに1回」端数処理をする、とされています。商品1行ごとに丸めてから合計するのか、合計してから丸めるのかで、結果が変わることがあります。

詳細は国税庁の案内で確認してください。プログラムを書く側の責任は、次の3点です。

  1. 端数処理の方法を、仕様として明文化してもらう(推測で決めない)。
  2. 金額は int(円)か Decimal で計算し、float と round() を使わない。
  3. 境界のテスト(.5 ちょうど、0円、大きな金額)を用意する(→ 2-04 テスト)。

float が正しい道具である場面

誤解のないように付け加えると、float 自体は欠陥品ではありません。成約率、平均応答時間、類似度のスコア、機械学習の計算など、「ごくわずかな誤差が問題にならない」量には float が適しています。速く、扱いやすいからです。LLM の temperature(応答のばらつき具合)や、埋め込みベクトルの類似度(→ 4-08 ベクトルと埋め込み)も float です。

問題は「float どうしを == で比べる」ことと「float の金額を切り捨てる」ことです。float を比べたいときは、差が十分に小さいかを調べます。

アプリで実行できるコード

import math
print(math.isclose(0.1 + 0.2, 0.3))
print(abs((0.1 + 0.2) - 0.3) < 1e-9)

1e-9 は 0.000000001(10のマイナス9乗)を表す書き方です。科学技術計算や機械学習のコードでよく見かけます。

型変換:文字列から数へ、数から文字列へ

外から入ってくるデータは、ほぼ文字列です。計算するには数に変換し、表示するには文字列に変換します。型の名前(int、float、str)を関数のように使うと変換できます。

アプリで実行できるコード

qty_text = "3"          # フォームから来た数量
price_text = "1980"     # CSVから来た単価
qty = int(qty_text)
price = int(price_text)
print(qty * price)
print("合計: " + str(qty * price) + "円")

int("3") は文字列 "3" を整数 3 に変換し、str(5940) は整数を文字列 "5940" に変換します。前の節で TypeError になった "合計: " + 5940 は、str() で文字列にしてから連結すれば動きます。もっと読みやすい書き方(f文字列)は次の章で学びます(→ 1-02 文字列を扱う)。

変換できるもの、できないもの

変換は、いつも成功するわけではありません。予想してください。どれがエラーになるでしょうか(エラーになる行があると、そこで実行が止まるので、この例ではエラーにならないものだけを並べています)。

実行前に出力を予想する

print(int(" 42 "))
print(int("123"))
print(int(3.99))
print(float("1e3"))
print(bool("False"))

1行目:前後の空白は無視されるので 42 になります。

2行目:全角数字の "123" も、Python は数字として理解し 123 に変換します。日本語の入力フォームでは全角数字が混ざりがちなので、知っておくと役立つ性質です(ただし全角のカンマやマイナス記号は別問題です。→ 1-02 文字列を扱う で扱う正規化が必要になります)。

3行目:int(3.99) は四捨五入ではなく、小数点以下の切り捨てで 3 です。

4行目:"1e3" は「1×10の3乗」なので 1000.0 です。

5行目が最大の落とし穴です。bool("False") は True になります。bool() は文字列の中身を読まず、「空の文字列なら False、1文字でもあれば True」と判定するからです。CSVやフォームで "False" や "0" という文字が届いたとき、bool() で変換すると全部 True になってしまいます。正しくは text == "True" のように、文字列として比較します。

一方、次のような変換はエラーになります。

int("1,280")     # カンマ入り
int("3.5")       # 小数の文字列を int に
int("三")        # 漢数字
int("")          # 空文字
float("abc")
ValueError: invalid literal for int() with base 10: '1,280'

値エラー(ValueError)は「型は合っているが、中身が受け付けられない」という意味です。int("3.5") が失敗する点は意外かもしれません。int() に渡す文字列は、整数の形をしている必要があります。小数の文字列を整数にしたいなら int(float("3.5")) のように2段階で変換します。カンマ入りの金額は、カンマを取り除いてから変換します("1,280".replace(",", "") で "1280" になります。→ 1-02 文字列を扱う)。

変換の失敗は、入力のせいとは限らない

症状:テストデータでは動いたのに、本番のCSVで ValueError が出る。

原因:実データには、カンマ入りの数("1,280")、全角や空白混じり、空欄("")、"-" や "N/A" のような「値なし」の記号が混ざります。

対処:変換する前に、実データを何件か print(repr(値)) で見る。どんな形の入力がありうるかを仕様として決める。変換に失敗したときの扱い(その行を飛ばすのか、エラーとして報告するのか)を決める(→ 1-08 エラーを読み、直す、→ 1-11 ファイル・JSON・CSV)。

型は「入口」で確定させる

実務のコードでは、外から来たデータを、受け取った直後に一度だけ変換・検証するのが原則です。CSVを読んだ直後、フォームを受け取った直後、LLMの返答を受け取った直後です。そこから先のコードは「price は必ず int」と信じて書けるようになります。

逆に、文字列のままプログラムの奥まで運び、使う場所ごとに int() を書くと、変換し忘れる箇所が必ず出ます。どこかで "1980" + "20" のような連結が起き、しかもエラーにならずに静かに間違った値が流れます。この「入口で型を確定させる」考え方は、→ 1-13 型ヒントと Pydantic で道具として整います。AIにコードを書かせるときも「入力は最初に int に変換し、変換できなければエラーにする」と指示に含めると、コードの見通しがよくなります。

比較演算と真偽値

2つの値を比べる演算子は、結果として True か False(bool 型)を返します。

演算子 意味 例 結果
== 等しい 1980 == 1980 True
!= 等しくない 1980 != 2200 True
< > より小さい/大きい 3 < 5 True
<= >= 以下/以上 5 >= 5 True

= は代入、== は比較です。この2つの取り違えは、初心者が最もよくする間違いの1つです。Python では if x = 5: のように書くと SyntaxError(文法エラー)になるので、気づきやすくはなっています。

比較の結果も値なので、変数に入れられます。

アプリで実行できるコード

monthly_sales = 1_250_000
target = 1_000_000
achieved = monthly_sales >= target
print(achieved, type(achieved))
print(0 <= 75 <= 100)    # 範囲のチェックはつなげて書ける

1_250_000 のアンダースコアは、桁を読みやすくするための区切りで、1250000 と同じ値です。金額や件数の定数を書くときに便利です。0 <= 75 <= 100 のように比較をつなげると「0以上かつ100以下」という意味になります。数学の書き方と同じです。

型が違うものの比較

予想してください。

実行前に出力を予想する

print(1 == 1.0)
print(1980 == "1980")
print("10" < "9")
print("apple" < "banana")

1 == 1.0 は数として等しいので True です。1980 == "1980" は、型が違うので False です。エラーにはならず、黙って False を返す点が危険です。CSVから読んだ文字列の商品コードと、プログラム内の整数の商品コードを比べると、見た目が同じでも一致しません。

"10" < "9" は True です。文字列の比較は、数の大小ではなく、先頭の文字から1文字ずつ辞書順で比べます。"1" は "9" より前なので、2文字目を見る前に決着します。売上の数字を文字列のまま並べ替えると、"10000" が "9000" より小さいと判定されて、ランキングがおかしくなります。数として比べたいなら、先に数に変換します。

bool は int の一種

Python では、True は 1、False は 0 として計算に使えます。True + True は 2 です。これを利用して、「条件を満たした件数」を sum() で数えるコードがよく書かれます(→ 1-09 内包表記とよく使う組み込み関数)。読めれば十分ですが、True と 1 が == で等しいことは覚えておくと、思わぬ一致に気づけます。

名前の付け方とコメント

コードは、書かれる回数より読まれる回数のほうがずっと多いものです。AIが書いたコードを自分が読み、自分が直したコードをAIが読みます。読み手にとって、変数名は最大の手がかりです。

Python の命名規則

Python の公式スタイルガイド(PEP 8)では、変数名はスネークケース(snake_case)で書きます。小文字の英単語をアンダースコアでつなぐ書き方です。

# 良い例
unit_price = 1980
monthly_sales = 1_250_000
is_member = True
inquiry_text = "返品したいのですが"

# 変更しない値(定数)は大文字
TAX_RATE_PERCENT = 10
MAX_RETRY = 3

# 避けたい例
x = 1980            # 何の値か分からない
UnitPrice = 1980    # クラス名の書き方(1-12)と紛らわしい
tanka = 1980        # ローマ字は読み手によって通じない

名前の規則のうち、守らないとエラーになるもの(数字で始められない、- や空白を含められない、if や for などの予約語は使えない)と、守らないと読みにくくなるだけのもの(スネークケース、意味のある名前)があります。後者も同じくらい大切です。日本語の変数名(単価 = 1980)も文法上は使えますが、チームやツールとの相性から、業務のコードでは英語にするのが一般的です。

組み込みの名前を上書きしない

Python には最初から使える名前(print、type、int、str、sum、list など)があります。これらに代入すると、本来の機能が使えなくなります。

sum = 1980 + 2200     # 合計を sum という名前で保存した
print(sum([1, 2, 3])) # 後で本来の sum() を使おうとすると……
TypeError: 'int' object is not callable

「int 型の値は呼び出せません」という、一見わけの分からないエラーです。sum という札が、合計関数から整数 4180 に付け替えられたのが原因です。total、sales_sum のように別の名前を使えば防げます。type、id、input、list、dict、str も、うっかり変数名にしがちなので注意してください。

コメントは「なぜ」を書く

# から行末まではコメントで、Python は無視します。人間(とAI)への説明です。

# 悪いコメント:コードを読めば分かることを繰り返している
tax = price * 10 // 100  # price に 10 を掛けて 100 で割る

# 良いコメント:コードからは分からない理由を書く
# 経理規程により、消費税は1円未満切り捨て(float を通すと1円ずれるため整数で計算)
tax = price * 10 // 100

コメントは「何をしているか」ではなく「なぜそうしているか」を書きます。「何を」は、良い変数名とコードそのものが語るべきことです。

AIが書いたコードの読み方と、LLMアプリでの値と型

この課程では、コードの多くをAIに書かせることになります。そのとき、この章の知識は「読んで危ない箇所を見つける目」として働きます。

この書き方を見たらこう読む

AIが書いたコードには、次のような書き方がよく出てきます。

TAX_RATE = 0.1
price: int = 1980
total = int(price * (1 + TAX_RATE))
count += 1
amount = Decimal(str(row["amount"]))

1行ずつ、こう読みます。

AIは、浮動小数点で金額を計算するコードを、ごく自然に書きます。学習データに price * 1.1 のようなコードが大量に含まれているからです。動くし、ほとんどのテストも通ります。だからこそ、読む側が気づく必要があります。

LLMアプリでは、値と型がこう現れる

LLM(大規模言語モデル)を使うアプリでは、この章の型がそのまま登場します。アプリ内の模擬LLM(llmsim)を使って確かめます。これは本物のLLMではなく、決まったルールで応答する練習用の模擬モデルです。呼び出し方と返ってくるデータの形は、本物の API(OpenAI 互換の Chat Completions API)と同じになっています。

アプリで実行できるコード

from llmsim import OpenAI

client = OpenAI()   # 模擬モデル。本物の LLM は呼びません
resp = client.chat.completions.create(
    model="sim-small",
    messages=[{"role": "user", "content": "注文番号12345の在庫数を教えてください"}],
)
answer = resp.choices[0].message.content
print(answer)
print(type(answer))
print(resp.usage.prompt_tokens, type(resp.usage.prompt_tokens))
print(client.total_cost, type(client.total_cost))

コードの細かい書き方は、今は分からなくてかまいません(→ 6-01 LLM API の基本 で学びます)。注目してほしいのは型です。

もう1つ、LLMアプリの設計で大切な原則があります。正確さが必要な計算は、LLMにさせずにコードで行うことです。LLMは文章を確率的に生成する仕組みなので、桁の多い掛け算や端数処理を、もっともらしいが間違った数字で答えることがあります(→ 5-05 LLMの得意・不得意と幻覚)。見積もり金額を答えるチャットボットなら、LLMには「どの商品を何個」という情報を読み取らせ、金額の計算はこの章で学んだ整数の計算で行います。LLMとコードの役割分担は、この課程を通して繰り返し出てくる考え方です。

この章の内容をAIと一緒に学ぶ

AIは「説明させる」「問題を出させる」使い方をすると、学習の助けになります。答えのコードだけをもらうと、自分で読めるようにはなりません。

おすすめの使い方は3つです。

  1. 予想が外れた predict ブロックをAIに貼り、「なぜこの出力になるのか、初心者向けに、仕組みから説明して」と頼む。説明を読んだら、自分の言葉で説明し直して、AIに間違いを指摘してもらう。
  2. 「型と型変換について、出力を予想する問題を5問出して。答えは私が答えてから見せて」と頼む。
  3. AIが書いた金額計算のコードを貼り、「float の誤差で1円ずれる箇所がないか確認して」と頼む。

AIが間違えやすい点も知っておいてください。round() を「四捨五入する関数」と説明してしまうことがあります。また、Decimal(0.1) のような誤った使い方を、正しいコードとして提示することもあります。AIの説明は、必ず実際に実行して確かめてください。

AIへの指示の例:予想問題を出してもらう
あなたはPythonの家庭教師です。私はプログラミング初心者で、「値・型・変数」を学んでいます。
次の範囲から、出力を予想する問題を1問ずつ出してください:{int と float の割り算 / 型変換 / 変数の再代入 / 浮動小数点の誤差}
ルール:
- 問題は3〜5行の短いコードにする
- 私が答えるまで正解を見せない
- 私の答えが間違っていたら、正解を言う前に、どこで考え違いをしたかのヒントを1つ出す
- 業務の例(売上、在庫、顧客)を使う

演習

演習:注文金額を計算する

次の注文について、小計・消費税・税込合計を計算してください。

subtotal(小計)、tax(消費税)、total(税込合計)という変数に、それぞれ int の値を代入してください。最後に print で税込合計を表示します。

解答例
unit_price = 1980
quantity = 3

subtotal = unit_price * quantity
tax = subtotal * 10 // 100
total = subtotal + tax

print("税込合計:", total)

演習:CSVの文字列を数に直して集計する

CSVファイルから、ある商品の1行を読み込んだところ、値がすべて文字列で届きました。

単価と数量を int に変換して price、qty に代入し、amount = price * qty を計算してください。会員かどうかは is_member に bool で代入します。bool(member_text) を使ってはいけません(理由は本文参照)。

カンマの除去には price_text.replace(",", "") を使えます(詳しくは次の章で学びます)。

解答例
price_text = "1,280"
qty_text = " 4 "
member_text = "False"

price = int(price_text.replace(",", ""))
qty = int(qty_text)
amount = price * qty
is_member = member_text == "True"

print(price, qty, amount, is_member)

演習:箱の数と端数を求める

倉庫から商品を出荷します。1箱に per_box 個入ります。次の2つの注文について、必要な箱の数(端数があれば1箱追加)と、最後の箱に入る個数を求めてください。

変数 boxes_a、boxes_b に必要な箱の数を、last_a、last_b に最後の箱に入る個数を代入してください。ちょうど割り切れるときは、最後の箱は満杯(12個)です。整数の演算(//、%、+、-)だけを使ってください。

解答例
per_box = 12
order_a = 50
order_b = 48

boxes_a = (order_a + per_box - 1) // per_box
boxes_b = (order_b + per_box - 1) // per_box
last_a = order_a - (boxes_a - 1) * per_box
last_b = order_b - (boxes_b - 1) * per_box

print(boxes_a, last_a, boxes_b, last_b)

演習:AIが書いた割引計算のバグを直す

AIに「会員は30%引き、1円未満切り捨て」の計算を書かせたところ、次のコードが出てきました。1,300円の商品で 909円になってしまいます(正しくは 910円)。

float を使わない計算に直してください。変数 discounted_1300、discounted_1980、discounted_99 に、それぞれ 1,300円、1,980円、99円の割引後の金額を代入します。

解答例
DISCOUNT_PERCENT = 30

discounted_1300 = 1300 * (100 - DISCOUNT_PERCENT) // 100
discounted_1980 = 1980 * (100 - DISCOUNT_PERCENT) // 100
discounted_99 = 99 * (100 - DISCOUNT_PERCENT) // 100

print(discounted_1300, discounted_1980, discounted_99)

演習:営業日報の数字をまとめる

営業日報のフォームから、次の値が文字列で届きました。

次の値を計算し、それぞれの変数に代入してください。

  1. visits、deals、sales、target:それぞれ int に変換した値
  2. close_rate:成約率(%)。成約件数 ÷ 訪問件数 × 100 を float で求める(ここでは誤差が問題にならないので float でよい)
  3. avg_deal:成約1件あたりの平均売上(円、1円未満切り捨て、int)
  4. achieved:売上が目標以上なら True、そうでなければ False
  5. shortfall:目標に対する不足額(円)。達成していれば 0

最後に、次の3行をこの形で表示してください(数値は計算結果)。

成約率: 25.0 %
平均単価: 416666 円
目標達成: True
解答例
visits_text = "12"
deals_text = " 3 "
sales_text = "1,250,000"
target_text = "1,000,000"

# ① 入力を数に変換する
visits = int(visits_text)
deals = int(deals_text)
sales = int(sales_text.replace(",", ""))
target = int(target_text.replace(",", ""))

# ② 指標を計算する
close_rate = deals / visits * 100
avg_deal = sales // deals
achieved = sales >= target
shortfall = max(target - sales, 0)

# ③ 結果を表示する
print("成約率:", close_rate, "%")
print("平均単価:", avg_deal, "円")
print("目標達成:", achieved)

理解度チェック

次のコードの出力は?

a = 100
b = a
a = a + 50
print(b)
  1. ① 150
  2. ② 100
  3. ③ 50
  4. ④ エラーになる
解答と解説

正解:②

b = a は「a が指している値 100 に b の札も付ける」という意味です。その後 a は新しい値 150 に付け替えられますが、b の札は 100 に付いたままです。「変数どうしがつながる」わけではありません。

print(7 / 7) の出力は?

  1. ① 1
  2. ② 1.0
  3. ③ 0
  4. ④ 7
解答と解説

正解:②

/ は割り切れる場合でも、結果は必ず float になります。整数の結果がほしいなら 7 // 7 と書きます。

1,300円の3割引後の金額(1円未満切り捨て)を求めるコードとして、最も適切なのは?

  1. ① int(1300 * 0.7)
  2. ② round(1300 * 0.7)
  3. ③ 1300 * 70 // 100
  4. ④ 1300 * 0.7 // 1
解答と解説

正解:③

1300 * 0.7 は 909.9999999999999 になるため、int() や // 1 で切り捨てると 909 になります。round() はこの例では 910 になりますが、切り捨てではなく丸めなので仕様と違い、別の金額で誤差が出ます。整数で掛けてから割る 1300 * 70 // 100 なら float を通らず、正確に 910 になります。

bool("False") の値は?

  1. ① False
  2. ② True
  3. ③ None
  4. ④ ValueError になる
解答と解説

正解:②

bool() は文字列の中身を読まず、空文字列 "" のときだけ False、それ以外はすべて True を返します。文字列の中身で判定したいときは text == "True" のように比較します。

次のうち、ValueError になるものはどれ?

  1. ① int(" 42 ")
  2. ② int("123")
  3. ③ int("3.5")
  4. ④ float("3.5")
解答と解説

正解:③

int() に渡す文字列は、整数の形をしている必要があります。"3.5" は小数の形なので失敗します。前後の空白や全角数字は受け付けられます。小数の文字列を整数にしたいなら int(float("3.5")) と2段階にします。

0.1 + 0.2 == 0.3 が False になる理由として正しいのは?

  1. ① Python の足し算にバグがあるため
  2. ② == は小数の比較に対応していないため
  3. ③ 0.1 や 0.2 は2進数で有限の桁に表せず、保存した時点でわずかにずれているため
  4. ④ 0.3 が整数として扱われるため
解答と解説

正解:③

分母に 5 を含む 1/10 は、2進数では無限に続く小数になります。float はそれを最も近い値で保存するため、わずかなずれが生じ、足すとずれが表に出ます。Python 以外のほとんどの言語でも同じことが起きます。

AIが書いた次のコードを読んで、最も注意すべき点は?

sum = price_a + price_b
print("合計", sum)
  1. ① 組み込み関数 sum を上書きしており、後で sum() を使うとエラーになる
  2. ② print に複数の値を渡しているのでエラーになる
  3. ③ sum は予約語なので SyntaxError になる
  4. ④ 問題はない
解答と解説

正解:①

sum は予約語ではないので代入自体はできますが、組み込み関数の sum が使えなくなります。後で sum([...]) を呼ぶと TypeError: 'int' object is not callable という分かりにくいエラーになります。total などの別名を使います。

まとめ

参考資料

いずれも任意です。この章の内容はアプリ内で完結しています。

Groundwork AIエンジニア養成課程:AIエンジニアを目指す人のための全85章の課程。Pythonの基礎、Git・テスト、Claude CodeやCodexと一緒に開発する方法、LLMの仕組みとアプリ開発までを、ブラウザで動くPythonで。最初の6章は無料。全章は2,480円の買い切り。

アプリを無料で開く有料版と無料版の違い