input()で受け取った数字に1を足そうとしたら、いきなりTypeErrorが出た。Pythonを始めたばかりのころ、そんな経験をした方は多いのではないでしょうか。
画面上では同じ5に見えても、文字列の5と数値の5はPythonにとってまったく別物です。そこで必要になるのが、値の型を別の型に作り替える型変換という操作です。
型変換に使う関数は、int()、float()、str()、bool()の4つが基本になります。どれも組み込み関数なのでimportは要りませんが、思いどおりに変換されないパターンがいくつかあるのが悩ましいところです。
今回は4つの関数の基本から、つまずきやすい落とし穴、変換に失敗したときの安全な書き方まで、手元のPython 3.11で動かした結果とともに紹介します。
型変換とは何か¶
型とは、その値が数値なのか文字列なのかといった、値の種類のことです。Pythonの値には必ず型があり、型によってできる操作が決まっています。
たとえば数値同士なら足し算ができますが、文字列と数値はそのまま足せません。実際に試すと、次のようにエラーになります。
age = "20"
print(age + 1)
# TypeError: can only concatenate str (not "int") to str
エラーメッセージは、文字列には文字列しかつなげられません、と言っています。JavaScriptのように裏で勝手に変換してくれる言語もありますが、Pythonはそれをしません。
不親切に感じるかもしれませんね。ですが、暗黙の変換をしないおかげで、意図しない計算結果が静かに紛れ込む事故を防げるのです。
そこで、型を変えたいときは自分で関数を呼んで変換します。この書き方を、暗黙の変換と区別して明示的な型変換と呼ぶこともあります。
基本の4つの変換関数¶
まずは、よく使う4つの関数を表で整理しておきましょう。それぞれ、変換先の型の名前がそのまま関数名になっています。
| 関数 | 変換先 | 例 | 結果 |
|---|---|---|---|
| int() | 整数 | int("42") | 42 |
| float() | 小数(浮動小数点数) | float("3.14") | 3.14 |
| str() | 文字列 | str(100) | '100' |
| bool() | 真偽値 | bool(0) | False |
表の例をコードで動かすと、次のようになります。type()で型も一緒に確かめてみました。
print(int("42"), type(int("42"))) # 42 <class 'int'>
print(float("3.14"), type(float("3.14"))) # 3.14 <class 'float'>
print(str(100), type(str(100))) # 100 <class 'str'>
print(bool(0), type(bool(0))) # False <class 'bool'>
冒頭のエラーも、int()で変換してから足せば解決します。print(int(age) + 1)と書けば、ちゃんと21が表示されます。
input()は、キーボードから入力された値を必ず文字列として返す関数です。数値として使いたいなら、受け取った直後に変換するのが定番の流れになります。【関連記事】Pythonのinput関数を初心者向けに解説!キーボード入力を受け取る基本
int()で文字列を整数にするときの注意点¶
4つの中でいちばん出番が多く、いちばんつまずきやすいのがint()です。ここでは、知っておきたい挙動を順番に見ていきます。
小数を含む文字列はそのまま変換できない¶
意外に思われるかもしれませんが、int("3.5")はエラーになります。int()に渡す文字列は、整数として読める形でなければならないからです。
int("3.5")
# ValueError: invalid literal for int() with base 10: '3.5'
print(int(float("3.5"))) # 3
小数を含む文字列を整数にしたいときは、いったんfloat()で小数にしてから、int()に渡します。2段階で変換するイメージですね。
ほかにも、空文字やカンマ区切りの数字もValueErrorになります。int("")やint("1,000")は失敗するので、外部から受け取った値はとくに注意が必要です。
一方で、前後の空白や改行は自動で取り除いてくれます。int(" 42\n")は問題なく42になりますし、Python 3.6からはint("1_000")のようにアンダースコアで区切った書き方も受け付けます。
小数を整数にすると切り捨てになる¶
小数をint()で整数にすると、小数点以下は四捨五入ではなく切り捨てられます。正確には、0に近い方向へ切り捨てる動きです。
print(int(3.9)) # 3
print(int(-3.9)) # -3
print(round(3.9)) # 4
マイナスの数で-4ではなく-3になるのは、0の方向へ寄せているからです。四捨五入したいときは、round()を使いましょう。
ただし、round()は偶数への丸めという方式を採用しているので、round(2.5)は3ではなく2になります。小数の扱いにまつわる注意点は、こちらの記事で詳しく解説しています。【関連記事】Pythonの丸め誤差とは?初心者にもわかる原因と対策をエンジニア歴10年の視点で解説
私は10年ほどエンジニアとして開発に関わってきましたが、ポイント計算でint()の切り捨てを四捨五入だと思い込んでいたコードに出会ったことがあります。金額が1円ずれるという地味な不具合で、原因を突き止めるまでに半日かかりました。
2進数や16進数の文字列も変換できる¶
int()には、2つ目の引数で基数を指定できます。基数とは、何進数として読むかを表す数のことです。
たとえばint("ff", 16)は255に、int("101", 2)は5になります。基数に0を渡すと、0bや0xといった接頭辞を見て自動で判断してくれるので、int("0b101", 0)も5になります。
float()とstr()の使い方¶
float()は、文字列や整数を小数に変換する関数です。int()と違い、小数点を含む文字列もそのまま受け付けます。
指数表記の文字列にも対応していて、float("1e3")は1000.0になります。さらに、float("nan")やfloat("inf")のように、非数や無限大を表す特別な値も作れます。
この特別な値は、うっかり整数に変換しようとするとエラーになります。int(float("nan"))はValueErrorになるので、計算結果を整数に直す前には注意してください。
str()は、ほぼどんな値でも文字列に変換できる関数です。str(3.0)は'3.0'に、str(None)は'None'に、str(True)は'True'になります。
数値を文章に埋め込むだけなら、str()で変換してつなげるより、f文字列を使うほうが読みやすくなります。f"年齢は{age}歳です"のように書けば、変換は自動で行われます。
bool()が文字列を判定するときの罠¶
bool()は、値を真偽値のTrueかFalseに変換する関数です。0や空文字、空のリスト、NoneはFalseになり、それ以外はおおむねTrueになります。
ここでよくある勘違いが、文字列のFalseを変換するとFalseになると思ってしまうことです。実際には、中身が空でない文字列はすべてTrueになります。
print(bool("False")) # True
print(bool("0")) # True
print(bool("")) # False
bool()は文字列の中身の意味までは読み取りません。空かどうかだけを見ているので、"False"も"0"も、文字が入っている以上はTrueになるのです。
設定ファイルや環境変数からオンかオフかを読み取るときに、この罠にハマりがちです。文字列で受け取った値は、次のように中身を比べて判定するのが確実です。
def to_bool(text):
value = text.strip().lower()
if value in ("true", "1", "yes", "on"):
return True
if value in ("false", "0", "no", "off", ""):
return False
raise ValueError(f"真偽値として読めません: {text!r}")
print(to_bool("False")) # False
print(to_bool(" YES ")) # True
どちらにも当てはまらない値はエラーにしておくと、設定の書き間違いにすぐ気づけます。何がTrueでFalseになるのかをもっと知りたい方は、こちらの記事もどうぞ。【関連記事】Pythonの真偽値を深く知る!if文で意外とハマるNoneや空リストの挙動を解説
変換に失敗したときの安全な書き方¶
ユーザーの入力やファイルの中身は、いつも期待どおりの形とは限りません。そのまま変換するとプログラムが止まってしまうので、失敗に備えた書き方を覚えておきましょう。
try/exceptでValueErrorを受け止める¶
Pythonでは、変換できるか先に調べるより、試してみて失敗したら対処する書き方がよく使われます。変換の失敗はValueErrorとして通知されるので、それをtry/exceptで受け止めます。
def to_int(text, default=None):
try:
return int(text)
except (ValueError, TypeError):
return default
print(to_int("42")) # 42
print(to_int("abc")) # None
print(to_int(None, 0)) # 0
TypeErrorも一緒に受け止めているのは、Noneのように文字列でも数値でもない値が来たときに備えるためです。int(None)は、ValueErrorではなくTypeErrorになります。
失敗したときに既定値を返すか、エラーをそのまま上に伝えるかは、場面によって使い分けてください。入力ミスを利用者に知らせたいなら、黙って既定値にせず、メッセージを出すほうが親切です。
isdigit()で事前チェックするときの注意¶
変換の前に、文字列のisdigit()メソッドで数字かどうかを調べる方法もあります。ただし、この方法には抜け道があります。
isdigit()は、マイナス記号や小数点を含む文字列にはFalseを返します。つまり"-5"や"3.5"は、変換できるのに弾かれてしまいます。
さらに、上付きの数字の²はisdigit()でTrueになるのに、int("²")はValueErrorになります。事前チェックで漏れを完全に防ぐのは難しいので、最終的にはtry/exceptで受け止めるのが安心です。
先に調べる書き方と、試してから対処する書き方の考え方の違いは、こちらで解説しています。【関連記事】PythonのEAFP/LBYLとは?「許可より許しを求める」プログラミング哲学とは
知っておくと安心な細かい仕様¶
ここからは、ふだんは意識しなくてもよいけれど、知っておくと慌てずに済む仕様を紹介します。
ひとつ目は、全角数字の扱いです。int("123")は、全角の数字でもエラーにならずに123になります。
Unicodeで数字として定義されている文字なら、int()は受け付けてくれるからです。フォームの入力で全角数字が混じっても変換できるのは便利ですが、意図せず通ってしまう値がある点は覚えておきましょう。
ふたつ目は、とても長い数字の文字列には上限があることです。Python 3.11以降では、10進数で4300桁を超える文字列をint()で変換しようとすると、ValueErrorになります。
これは、巨大な数字の変換に時間がかかることを悪用した攻撃を防ぐための制限です。どうしても必要なときは、sys.set_int_max_str_digits()で上限を変えられます。
import sys
print(sys.get_int_max_str_digits()) # 4300
int("1" * 5000)
# ValueError: Exceeds the limit (4300 digits) for integer string conversion: ...
ふつうのアプリで4300桁の数字を扱うことはまずありません。ただ、外部から受け取った文字列をそのまま変換する処理では、こうした上限があること自体を知っておくと、エラーが出ても落ち着いて対処できます。
型変換を使いこなすためのコツ¶
最後に、実務で型変換を書くときに意識したいことを整理しておきます。
いちばん大事なのは、変換はデータの入り口で1回だけ行うことです。input()やファイル、APIから値を受け取った直後に変換し、その後の処理では変換済みの値だけを扱うようにします。
あちこちでint()を呼んでいると、どこかで変換し忘れたり、二重に変換したりする原因になります。入り口で型をそろえておけば、内側のコードは安心して計算に集中できます。
型がわからなくなったときは、print(type(x))で確かめる習慣をつけておくと便利です。型を判定する方法については、こちらの記事でも詳しく紹介しています。【関連記事】Pythonのisinstance()とtype()の違いとは?型チェックの正しい書き方と、boolがintに化ける落とし穴を初心者向けに解説
以前、CSVから読み込んだ金額を文字列のまま並べ替えてしまい、1000円が200円より前に来るという不具合を出したことがあります。文字列は数値の大小ではなく文字の順で比べられるので、読み込んだ直後に変換しておけば防げたミスでした。
まとめ¶
Pythonは文字列と数値を自動では変換してくれないので、int()、float()、str()、bool()で明示的に型を変える必要があります。input()で受け取った値は、必ず文字列だという点も押さえておきましょう。
int()は小数を含む文字列を受け付けず、小数を渡すと切り捨てになります。四捨五入したいならround()を使い、文字列の"3.5"はfloat()を経由して変換します。
bool()は文字列の中身を読まないので、"False"もTrueになります。設定値などを真偽値にしたいときは、中身を比べて判定する関数を用意すると安全です。
そして、変換の失敗にはtry/exceptで備え、変換はデータの入り口で1回だけ行う。この2つを意識するだけで、型にまつわるエラーはぐっと減るはずです。
まずは、手元のコードでint()を呼んでいる箇所を探して、変換に失敗したときのことまで考えられているか見直してみてはいかがでしょうか。
ここまでお読みいただきありがとうございました。