Pythonで温度変換プログラムを作る(摂氏・華氏・ケルビン)
摂氏・華氏・ケルビンを相互に変換するプログラムを、計算式だけの最短版から tkinter のGUI版まで順番に作ります。入力欄の文字を数値に変える float() のところでつまずく人が多いので、そこを重点的に解説します。
変換式だけ知りたい方は「2. 変換式は4本だけ」へ。GUI付きの完成コードが欲しい方は「4. 完全なソースコード」へ。could not convert string to float と出て困っている方は症状別チェックの該当項目に直接お進みください。
1. このページで作るもの
温度の単位変換は、プログラミングの練習題材として何度も登場します。式が単純で結果を検算しやすく、それでいて「入力を受け取る」「数値に変える」「失敗したときに知らせる」という実用アプリの骨格がひととおり入っているからです。
このページでは同じ変換を2段階で作ります。まず計算式だけの最短版で仕組みを押さえ、次に tkinter で入力欄とボタンを付けたGUI版に仕上げます。完成する画面が図1で、36 °C を変換したところです。表示されている 96.80 °F・309.15 K は、次章の変換式で手計算した値と一致します。並べたもう1枚は、同じコードを Linux Mint 22.3 で起動した直後の画面です。
読み終わったとき、あなたは次の3つができるようになります。①摂氏・華氏・ケルビンの相互変換を自分で書ける、②入力欄の文字を数値に変える流れを説明できる、③変な値を入れられたときにアプリが落ちないようにできる。3つ目が本題です。入力を受け取るアプリを作ると必ず「数字以外を打たれたらどうするか」という問題にぶつかるので、ここで型を覚えてしまうと以降のアプリ作りが一気に楽になります。
このページで扱う機能
- 摂氏・華氏・ケルビンの相互変換(式は全部で4本)
Entryウィジェットからの値の取り出しとfloat()による数値化try/exceptによるValueErrorの受け止めとmessageboxでの通知Comboboxによる単位の選択- 絶対零度など、物理的にありえない値のチェック
掲載しているコードは Windows 11 / Python 3.12.10(Tcl/Tk 8.6)で実際に実行し、変換結果は水の凝固点・沸点・絶対零度など答えが決まっている7点で検算しました(結果は2章に掲載)。仕様の説明は Python公式ドキュメントと Tcl/Tk のマニュアルで裏取りし、引用箇所には出典を添えています。図1は Windows 11、並べたもう1枚は Linux Mint 22.3(仮想マシン)で撮影したものです。Linux Mint 22.3 では起動して画面が表示されるところまで確認しました(検算と実測は Windows 11 のみです)。macOS は未検証です。
tkinter でウィンドウを出すこと自体がまだ不安な場合は、先にtkinterで最初のウィンドウを表示するを読んでおくと、この記事のコードがぐっと読みやすくなります。tkinter が入っているかどうかの確認方法も、そちらにまとめてあります。
2. 変換式は4本だけ(検算つき)
単位が3つあるので、素直に考えると変換の組み合わせは6通りです。しかしいったん摂氏にそろえてから他の2つを出すと決めてしまえば、必要な式は入り口2本・出口2本の合計4本で済みます。単位が増えても同じ設計で拡張できます。
4本の式
| 向き | 式 | Pythonでの書き方 |
|---|---|---|
| 華氏 → 摂氏 | °C = (°F − 32) × 5 / 9 | celsius = (fahrenheit - 32) * 5 / 9 |
| ケルビン → 摂氏 | °C = K − 273.15 | celsius = kelvin - 273.15 |
| 摂氏 → 華氏 | °F = °C × 9 / 5 + 32 | fahrenheit = celsius * 9 / 5 + 32 |
| 摂氏 → ケルビン | K = °C + 273.15 | kelvin = celsius + 273.15 |
数字の根拠も押さえておきましょう。米国国立標準技術研究所(NIST)は摂氏とケルビンの関係を「摂氏の1度は 1 K の間隔であり、摂氏0度は 273.15 K である」と説明し、ケルビンから摂氏への換算を K - 273.15 と示しています。華氏については「摂氏の1度の間隔は華氏の1.8度の間隔に対応する」とされ、摂氏から華氏への換算は (°C × 1.8) + 32 です。1.8 と 9 / 5 は同じ値なので、コードではどちらで書いても構いません。
出典: NIST「SI Units – Temperature」より翻訳。原文は英語(2026年8月16日確認)
まずは式だけ動かす
GUIの前に、式が合っていることを確かめます。次の4行をそのまま実行してください。
celsius = 25
fahrenheit = celsius * 9 / 5 + 32
kelvin = celsius + 273.15
print(fahrenheit, kelvin) # 77.0 298.15
25 °C が 77.0 °F・298.15 K になれば式は正しく書けています。ここまでが温度変換プログラムの本体で、残りは全部「入力の受け取り方」と「表示の仕方」の話です。
コマンドラインだけで完結させる版
GUIは要らない、標準入力で十分という場合はこれで完成です。GUI版の _calculate() とほぼ同じ処理をしています。
import math
def to_celsius(value, unit):
"""入力された温度を摂氏にそろえる"""
if unit == "C":
return value
if unit == "F":
return (value - 32) * 5 / 9
if unit == "K":
return value - 273.15
raise ValueError(f"単位は C・F・K のどれかです: {unit}")
def convert(text, unit):
"""文字列を受け取って (摂氏, 華氏, ケルビン) を返す"""
temp = float(text)
if not math.isfinite(temp):
raise ValueError("有限の数値を入力してください")
celsius = to_celsius(temp, unit)
if celsius < -273.15:
raise ValueError("絶対零度(-273.15 °C)より低い温度は存在しません")
return celsius, celsius * 9 / 5 + 32, celsius + 273.15
if __name__ == "__main__":
text = input("温度を入力してください: ")
unit = input("単位を入力してください(C / F / K): ").upper()
try:
c, f, k = convert(text, unit)
except ValueError as e:
print("入力値が不正です:", e)
else:
print(f"摂氏 {c:.2f} °C / 華氏 {f:.2f} °F / ケルビン {k:.2f} K")
実行すると次のようになります。100 と C を入力した場合です。
温度を入力してください: 100
単位を入力してください(C / F / K): C
摂氏 100.00 °C / 華氏 212.00 °F / ケルビン 373.15 K
convert() が計算だけを担当し、画面への表示を一切していない点に注目してください。この形にしておくと、次章以降でGUIを付けるときに計算部分をそのまま流用できます。
7点で検算する
温度変換は答えが公表されている値と突き合わせられるので、書いたら必ず検算します。assert を使うと、間違っていたときだけ止まるチェックが数行で書けます。上のファイルと同じフォルダに置いて実行してください。
import math
from temperature_cli import convert
CASES = [
("水の凝固点", "0", "C", 0.00, 32.00, 273.15),
("水の沸点", "100", "C", 100.00, 212.00, 373.15),
("人の平熱", "36.5", "C", 36.50, 97.70, 309.65),
("摂氏と華氏が一致", "-40", "C", -40.00, -40.00, 233.15),
("絶対零度", "-273.15", "C", -273.15, -459.67, 0.00),
("華氏の平熱表記", "98.6", "F", 37.00, 98.60, 310.15),
("室温をケルビンで", "300", "K", 26.85, 80.33, 300.00),
]
for name, text, unit, exp_c, exp_f, exp_k in CASES:
c, f, k = convert(text, unit)
assert math.isclose(c, exp_c, abs_tol=0.005), name
assert math.isclose(f, exp_f, abs_tol=0.005), name
assert math.isclose(k, exp_k, abs_tol=0.005), name
print(f"OK {name} {text}{unit} -> {c:.2f} °C / {f:.2f} °F / {k:.2f} K")
print("すべての検算に合格しました")
手元(Windows 11 / Python 3.12.10)で実行した結果がこちらです。
OK 水の凝固点 0C -> 0.00 °C / 32.00 °F / 273.15 K
OK 水の沸点 100C -> 100.00 °C / 212.00 °F / 373.15 K
OK 人の平熱 36.5C -> 36.50 °C / 97.70 °F / 309.65 K
OK 摂氏と華氏が一致 -40C -> -40.00 °C / -40.00 °F / 233.15 K
OK 絶対零度 -273.15C -> -273.15 °C / -459.67 °F / 0.00 K
OK 華氏の平熱表記 98.6F -> 37.00 °C / 98.60 °F / 310.15 K
OK 室温をケルビンで 300K -> 26.85 °C / 80.33 °F / 300.00 K
すべての検算に合格しました
摂氏と華氏の数値が一致するのは -40 だけです。-40 を入れて両方が -40.00 になれば、掛ける数と足す数の順序を間違えていないことがひと目で分かります。式を書き換えたときの動作確認に使えます。
3. 入力欄の文字が数値になるまで
ここからGUIの話です。式は合っているのに動かない、という相談のほとんどは計算ではなく入力欄から値を取り出すところで起きています。先に全体の流れを見ておきましょう。
get() が返すのは必ず文字列
Tcl/Tk のマニュアルは、entry ウィジェットの get について「エントリーの文字列を返す」と1行で説明しています。数字を打っても、返ってくるのは数値ではなく文字列です。ここを見落とすと、足し算のつもりが文字列の連結になって静かに間違った結果が出ます。
value = entry.get() # 画面に 25 と打っても、value は文字列の "25"
print(type(value)) # <class 'str'>
print(value + 5) # TypeError: can only concatenate str (not "int") to str
出典: Tcl/Tk 8.6 マニュアル ttk::entryより翻訳。原文は英語(2026年8月16日確認)
float() は「読めない文字列」を ValueError で知らせる
文字列を数値に変えるのが float() です。Python公式ドキュメントは「引数が文字列の場合、10進数でなければなりません。先頭に符号または空白を含んでもかまいません。」と説明しています。逆に言えば、この条件を満たさない文字列を渡すと変換できません。そのときに送出されるのが ValueError です。公式ドキュメントは ValueError を「演算子や関数が、正しい型だが適切でない値を持つ引数を受け取ったときや、IndexError のようなより詳細な例外では記述できない状況で送出されます。」と定義しています。
実際にどんな文字列が通り、どんな文字列で落ちるのかを Python 3.12.10 で確かめました。予想外のものが混じっています。
| 入力した文字列 | 結果 | 備考 |
|---|---|---|
"25" | 25.0 | 基本形 |
" 25 " | 25.0 | 前後の空白は無視される |
"+30" | 30.0 | 符号を前に付けてよい |
"2e3" | 2000.0 | 指数表記も通る |
"1_000" | 1000.0 | 桁区切りのアンダースコアが使える(3.6以降) |
"25" | 25.0 | 全角数字は通る |
"inf" / "nan" | inf / nan | エラーにならない(後述) |
""(空欄) | ValueError | could not convert string to float: '' |
"25,5" | ValueError | カンマは小数点として扱われない |
"12.5" | ValueError | 全角のピリオド(.)は通らない(後述) |
"25 5" | ValueError | 途中の空白は不可 |
"abc" | ValueError | could not convert string to float: 'abc' |
この表で覚えておきたいのは「全角数字は通るのに、全角のピリオド(.)は通らない」という一点です。日本語入力をオンにしたまま「25.5」と打つと、数字はそのままなのに小数点だけが理由でエラーになります。原因が分かりにくい失敗なので、7章の実験3で対策を扱います。
出典: Python公式ドキュメント 組み込み関数 float()/同 組み込み例外 ValueError(いずれも2026年8月16日確認)
失敗を受け止める型
変換が失敗しうると分かっているので、try と except で囲みます。GUIアプリでは、この受け止めが特に重要です。例外を放置すると、画面上は何も起きていないように見えるのに、裏側のターミナルにだけエラーが出るという一番デバッグしにくい状態になるからです。
try:
temp = float(value)
except ValueError:
messagebox.showerror("エラー", "数値を入力してください")
return
例外の基本的な考え方や、他のエラーメッセージの読み方はPythonエラー一覧&解決法の ValueError の章にまとめてあります。
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
GUI版の全体です。空行を含めて139行ありますが、そのうち61行は画面を組み立てる _build_ui() で、計算そのものを担当する _calculate() は33行しかありません。行数の大半はレイアウトです。まずは app003.py という名前で保存して実行し、動く画面を見てから読み進めるのが近道です。
import math
import tkinter as tk
from tkinter import ttk, messagebox
class App03:
"""温度変換アプリ"""
def __init__(self, root):
self.root = root
self.root.title("温度変換アプリ")
self.root.geometry("500x812")
self.root.minsize(500, 812)
self.root.configure(bg="#f8f9fc")
self._build_ui()
def _build_ui(self):
"""UIを構築する"""
# タイトルフレーム
title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
title_frame.pack(fill=tk.X)
tk.Label(
title_frame,
text="温度変換アプリ",
font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white"
).pack()
# メインフレーム
main_frame = tk.Frame(self.root, bg="#f8f9fc", padx=20, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 入力エリア
input_frame = ttk.LabelFrame(main_frame, text="入力", padding=10)
input_frame.pack(fill=tk.X, pady=(0, 10))
tk.Label(input_frame, text="温度:").grid(row=0, column=0, sticky="w")
self.entry = ttk.Entry(input_frame, width=20, font=("Arial", 12))
self.entry.grid(row=0, column=1, padx=8, sticky="ew")
self.entry.bind("<Return>", lambda e: self.process())
tk.Label(input_frame, text="単位:").grid(row=0, column=2, sticky="w", padx=(8, 0))
self.unit_var = tk.StringVar(value="摂氏 (°C)")
self.unit_combo = ttk.Combobox(
input_frame,
textvariable=self.unit_var,
values=["摂氏 (°C)", "華氏 (°F)", "ケルビン (K)"],
state="readonly",
width=14
)
self.unit_combo.grid(row=0, column=3, padx=8)
input_frame.columnconfigure(1, weight=1)
# 実行ボタン
ttk.Button(
main_frame,
text="変換する",
command=self.process
).pack(pady=8)
# 結果エリア
result_frame = ttk.LabelFrame(main_frame, text="結果", padding=10)
result_frame.pack(fill=tk.BOTH, expand=True)
self.result_text = tk.Text(
result_frame,
font=("Noto Sans JP", 11),
bg="white", relief=tk.FLAT,
state=tk.DISABLED
)
self.result_text.pack(fill=tk.BOTH, expand=True)
# スクロールバー
scrollbar = ttk.Scrollbar(result_frame, command=self.result_text.yview)
self.result_text.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
def process(self):
"""メイン処理"""
value = self.entry.get().strip()
if not value:
messagebox.showwarning("警告", "値を入力してください")
return
try:
result = self._calculate(value)
self._show_result(result)
except ValueError as e:
messagebox.showerror("エラー", f"入力値が不正です: {e}")
except Exception as e:
messagebox.showerror("エラー", f"予期しないエラー: {e}")
def _calculate(self, value):
"""温度変換の計算処理"""
temp = float(value)
# float("inf") や float("nan") は例外にならないので、ここで弾く
if not math.isfinite(temp):
raise ValueError("有限の数値を入力してください")
unit = self.unit_var.get()
# まず摂氏に変換
if unit == "摂氏 (°C)":
celsius = temp
elif unit == "華氏 (°F)":
celsius = (temp - 32) * 5 / 9
else: # ケルビン
if temp < 0:
raise ValueError("ケルビンは0以上の値を入力してください")
celsius = temp - 273.15
if celsius < -273.15:
raise ValueError("絶対零度(-273.15 °C)より低い温度は存在しません")
fahrenheit = celsius * 9 / 5 + 32
kelvin = celsius + 273.15
return (
f"【変換結果】\n"
f"\n"
f"摂氏 (°C): {celsius:.2f} °C\n"
f"華氏 (°F): {fahrenheit:.2f} °F\n"
f"ケルビン (K): {kelvin:.2f} K"
)
def _show_result(self, text):
"""結果テキストエリアに表示"""
self.result_text.config(state=tk.NORMAL)
self.result_text.delete("1.0", tk.END)
self.result_text.insert("1.0", text)
self.result_text.config(state=tk.DISABLED)
if __name__ == "__main__":
root = tk.Tk()
app = App03(root)
root.mainloop()
この完成コードは minsize(500, 812) で最小サイズを固定しているので、これより小さくは縮められません。
5. コード解説(1行ずつ)
上から順に、意味のあるまとまりごとに分解します。変数や関数の書き方そのものに不安があれば、Pythonの基本構文と関数の使い方もあわせて確認してください。
5-1. import: 3行で4つのモジュールを読み込む
import math
import tkinter as tk
from tkinter import ttk, messagebox
math は inf や nan を弾くために使います。tkinter はGUIの本体、ttk はOSのテーマに見た目を合わせたウィジェット群、messagebox はエラーを知らせるダイアログです。すべて標準ライブラリなので追加インストールは要りません。
5-2. クラスにまとめる理由
class App03:
def __init__(self, root):
self.root = root
self.root.title("温度変換アプリ")
self.root.geometry("500x812")
self._build_ui()
入力欄・単位の選択・結果表示という3つの部品を、別々のメソッドから触る必要があります。関数だけで書くと global だらけになるので、self. で持たせてしまうほうが早いです。役割は __init__() がウィンドウ設定、_build_ui() が画面の組み立て、process() がボタンを押されたときの入口、_calculate() が計算、_show_result() が表示、と1メソッド1仕事に分けています。
_build_ui のように名前を _ で始めるのは「クラスの内部でだけ使うつもり」という書き手の意思表示です。Python公式のコーディング規約 PEP 8 は、先頭に1つ付けたアンダースコアを weak "internal use" indicator(内部利用を示す弱い指標)と説明しています。文法上のアクセス制限ではなく、あくまで慣習です。
出典: PEP 8 – Style Guide for Python Code(Descriptive: Naming Styles)より翻訳。原文は英語(2026年8月16日確認)
5-3. Entry: 入力欄を作る
self.entry = ttk.Entry(input_frame, width=20, font=("Arial", 12))
self.entry.grid(row=0, column=1, padx=8, sticky="ew")
self.entry.bind("<Return>", lambda e: self.process())
1行目で入力欄を作り、2行目で配置しています。作成と配置を必ず2行に分けてください。self.entry = ttk.Entry(...).grid(...) と1行にまとめると、self.entry には grid() の戻り値である None が入ってしまい、あとで self.entry.get() を呼んだ瞬間にエラーになります(症状別チェック参照)。
3行目の bind("<Return>", ...) はEnterキーへの割り当てです。<Return> がキーボードのEnterキー、<Enter> は「マウスがウィジェットに乗った」という別のイベントなので、名前が紛らわしい部分です。lambda e: と引数を1つ受け取っているのは、bind() で呼ばれる関数にはイベント情報のオブジェクトが必ず1つ渡されるためです。
5-4. Combobox と StringVar: 単位を選ばせる
self.unit_var = tk.StringVar(value="摂氏 (°C)")
self.unit_combo = ttk.Combobox(
input_frame,
textvariable=self.unit_var,
values=["摂氏 (°C)", "華氏 (°F)", "ケルビン (K)"],
state="readonly",
width=14
)
StringVar は、ウィジェットの表示と Python 側の値を連動させるための入れ物です。公式ドキュメントは「ウィジェットは Variable オブジェクトにのみ結び付けられ、普通のPython変数には結び付けられない」と説明しています。textvariable に渡しておくと、利用者が選択を変えた瞬間に self.unit_var.get() の返り値も変わります。value= で初期値を指定しているので、起動直後から「摂氏 (°C)」が選ばれた状態になります。
state="readonly" は、公式ドキュメントによれば「値を直接編集できず、利用者はドロップダウンの一覧から1つを選ぶことしかできない」状態です。手入力を許すと想定外の文字列が入り、単位の比較が成立しなくなるので、選択肢が決まっているものは readonly にしておくのが安全です。
出典: Python公式ドキュメント tkinter/同 tkinter.ttk(Combobox の state オプション)より翻訳。原文は英語(いずれも2026年8月16日確認)
5-5. process(): 入口で空欄をふるい落とす
def process(self):
value = self.entry.get().strip()
if not value:
messagebox.showwarning("警告", "値を入力してください")
return
try:
result = self._calculate(value)
self._show_result(result)
except ValueError as e:
messagebox.showerror("エラー", f"入力値が不正です: {e}")
except Exception as e:
messagebox.showerror("エラー", f"予期しないエラー: {e}")
.strip() で前後の空白を落としてから、if not value: で空欄を判定しています。空文字列は偽と評価されるので、if value == "": と書かなくてもこれで足ります。空欄のときだけ showerror ではなく showwarning を使い分けているのは、「打ち間違い」ではなく「まだ何も入れていない」状態だからです。
except を2段構えにしている点も見てください。上の ValueError は想定内の失敗(数字以外を打たれた、ありえない温度だった)で、利用者に直せる内容を伝えます。下の Exception は想定外の失敗で、ここに来る時点でこちらのコードの不備ですが、それでもアプリを落とさずメッセージを出します。順番を逆にすると Exception がすべて先に捕まえてしまい、丁寧なメッセージが出せなくなるので入れ替えないでください。
5-6. _calculate(): 摂氏にそろえてから計算する
temp = float(value)
# float("inf") や float("nan") は例外にならないので、ここで弾く
if not math.isfinite(temp):
raise ValueError("有限の数値を入力してください")
unit = self.unit_var.get()
if unit == "摂氏 (°C)":
celsius = temp
elif unit == "華氏 (°F)":
celsius = (temp - 32) * 5 / 9
else: # ケルビン
if temp < 0:
raise ValueError("ケルビンは0以上の値を入力してください")
celsius = temp - 273.15
2章で決めたとおり、まず入力を摂氏にそろえます。ケルビンだけ先にマイナスを弾いているのは、「マイナスのケルビン」という概念自体が存在しないので、絶対零度のチェックより手前で止めたほうがメッセージが具体的になるからです。
math.isfinite() の行は、実際に動かして見つけた穴をふさぐためのものです。float("nan") は例外にならずに nan という特別な値を返し、しかも nan < -273.15 は False になります。つまりこの行がないと、nan と打った入力が下の絶対零度チェックを素通りして「nan °C」と表示されてしまいます。詳しくは症状別チェックで扱います。
5-7. 表示は書式指定で丸める
fahrenheit = celsius * 9 / 5 + 32
kelvin = celsius + 273.15
return (
f"【変換結果】\n"
f"\n"
f"摂氏 (°C): {celsius:.2f} °C\n"
f"華氏 (°F): {fahrenheit:.2f} °F\n"
f"ケルビン (K): {kelvin:.2f} K"
)
{celsius:.2f} は「小数点以下2桁で表示する」という書式指定です。飾りではなく必需品で、これを外すと絶対零度の変換結果が -459.66999999999996 と表示されます。後述のとおり、これは計算ミスではなく浮動小数点の性質です。
f文字列を ( と ) で囲んで複数行に分けているのは、隣り合った文字列リテラルが自動的に連結されるPythonの性質を使った書き方です。1行を短く保てるので、長いメッセージを組み立てるときに便利です。
5-8. _show_result(): 編集禁止のまま書き換える
def _show_result(self, text):
self.result_text.config(state=tk.NORMAL)
self.result_text.delete("1.0", tk.END)
self.result_text.insert("1.0", text)
self.result_text.config(state=tk.DISABLED)
結果欄の Text ウィジェットは state=tk.DISABLED で作ってあり、利用者が勝手に書き換えられないようにしています。ただしこの状態ではプログラムからも書き込めません。そこで、いったん NORMAL に戻して書き換え、すぐ DISABLED に戻す、という手順を踏んでいます。この4行はセットで覚えてしまってください。
"1.0" は Text ウィジェット独特の位置指定で、「1行目の0文字目」つまり先頭を意味します。行番号が1始まり、文字位置が0始まりという組み合わせなので、最初は戸惑うところです。delete("1.0", tk.END) で全消去してから insert() するため、変換のたびに前の結果は消えます。
6. 式1本から作る7ステップ
139行をいきなり写経すると、どこで失敗したのか分からなくなります。まず計算だけを print() で確かめ、そのあとで画面を付ける順番がおすすめです。各ステップで必ず実行して、動くことを確認してから次に進んでください。
-
1計算だけを関数にする
GUIのことは忘れて、まず変換関数だけを書きます。
print()で結果を見て、-40を入れたら華氏も-40.0になることを確かめましょう。def to_fahrenheit(celsius): return celsius * 9 / 5 + 32 print(to_fahrenheit(0)) # 32.0 print(to_fahrenheit(100)) # 212.0 print(to_fahrenheit(-40)) # -40.0 -
2空のウィンドウを出す
新しいファイルを
app003.pyという名前で保存し、この3行で灰色のウィンドウが出ることを確認します。ここが動かない場合は環境側の問題なので、tkinterが使えるかの確認手順に戻ってください。import tkinter as tk root = tk.Tk() root.mainloop() -
3入力欄とボタンを置く
root = tk.Tk()とroot.mainloop()の間に書き足します。以降のステップもすべてこの間に追加していきます。押したらターミナルに文字が出るだけの、いちばん簡単な形で確認します。from tkinter import ttk entry = ttk.Entry(root, width=20) entry.pack(pady=10) ttk.Button(root, text="変換する", command=lambda: print("clicked!")).pack() -
4入力欄の値を取り出す
ボタンの処理を関数に変え、打ち込んだ文字が取り出せているか
print()で確認します。type()も一緒に出すと、返ってくるのが文字列だと実感できます。def on_click(): value = entry.get() print(repr(value), type(value)) ttk.Button(root, text="変換する", command=on_click).pack() -
5数値に変えて計算し、画面に出す
結果表示用のラベルを1つ足して、そこに書き込みます。ここまでで「入力 → 計算 → 表示」が一周します。
result_label = ttk.Label(root, text="") result_label.pack(pady=10) def on_click(): celsius = float(entry.get()) result_label.config(text=f"{celsius * 9 / 5 + 32:.2f} °F") -
6わざと文字を入れて落としてみる
ステップ5の状態で「あ」と入力してボタンを押してください。画面には何も起きず、ターミナルにだけ赤い文字が出るはずです。これがGUIアプリのエラーの見え方です。確認できたら
try/exceptで受け止めます。from tkinter import messagebox def on_click(): try: celsius = float(entry.get()) except ValueError: messagebox.showerror("エラー", "数値を入力してください") return result_label.config(text=f"{celsius * 9 / 5 + 32:.2f} °F") -
7単位の選択を足して完成形に寄せる
最後に Combobox を追加し、選ばれた単位で処理を分岐させます。ここまで来たら4章の完成コードと見比べて、クラス化・レイアウト・絶対零度チェックを取り込んでください。
unit_var = tk.StringVar(value="摂氏 (°C)") ttk.Combobox( root, textvariable=unit_var, values=["摂氏 (°C)", "華氏 (°F)", "ケルビン (K)"], state="readonly", ).pack()
VSCodeで実行ボタンが出ない・Pythonが選べないといった場合は、VSCodeのPython環境セットアップで拡張機能とインタープリターの設定を確認してください。
7. 自分でいじって試す5つの実験
動いたら、次は意図的に書き換えて結果を見る番です。どれも数分で終わります。1か所ずつ変えて実行すると、「この行が結果のここに効いている」という感覚が短時間で身につきます。
実験1: 小数点以下の桁数を変える
:.2f の数字を書き換えるだけです。桁を減らすと丸められ、増やすと浮動小数点の素顔が見えてきます。
value = 26.85
print(f"{value:.0f}") # 27
print(f"{value:.1f}") # 26.9
print(f"{value:.2f}") # 26.85
print(f"{value:.10f}") # 26.8500000000
あわせて :.2f をすべて外して実行してみてください。-273.15 を変換したときの華氏が -459.66999999999996 になり、書式指定が単なる見栄えではないことが分かります。
実験2: 数字以外を打てないようにする
エラーを出すのではなく、そもそも入力させない方法です。Tcl/Tk の entry には検証の仕組みがあり、Pythonからは root.register() で関数を登録して使います。
def only_number(new_value):
"""入力途中の状態も許す、ゆるめの判定"""
if new_value in ("", "-", "+", ".", "-.", "+."):
return True
try:
float(new_value)
except ValueError:
return False
return True
vcmd = (root.register(only_number), "%P")
entry = ttk.Entry(root, validate="key", validatecommand=vcmd)
entry.pack()
validate="key" はキーを打つたびに検証するモードです。"%P" は Tcl/Tk が用意している置換コードで、マニュアルには「編集が受け入れられた場合の、entry の新しい値」と説明されています。つまり only_number() には「その1文字を打ったあとの文字列」が渡され、False を返すと入力が拒否されます。
入力途中の "-" や "25." まで弾いてしまうと打てなくなるので、最初の if で逃がしています。なお float() で判定しているため、"nan" と "inf" はこの検証を通ります。最終的なチェックはやはり _calculate() 側で必要です。
出典: Tcl/Tk 8.6 マニュアル ttk::entryより翻訳。原文は英語(2026年8月16日確認)
実験3: 全角で入力されても受け付ける
3章の表のとおり、全角数字は通るのに全角のピリオド(.)は通りません。日本語入力のまま打った利用者を救うには、変換の前に文字を正規化します。
import unicodedata
def normalize_number(text):
"""全角の数字・記号を半角に寄せる"""
t = unicodedata.normalize("NFKC", text)
return t.replace("−", "-").replace("ー", "-").strip()
print(float(normalize_number("12.5"))) # 12.5
print(float(normalize_number(" 36.5 "))) # 36.5
unicodedata.normalize("NFKC", ...) は全角英数字や全角の記号を半角に寄せる標準ライブラリの関数です。ただしNFKCだけでは足りない文字があります。実際に試すと、数学記号のマイナス(U+2212。全角ハイフン U+FF0D とは別の文字です)は NFKC を通しても変わらず、float() でエラーになりました。そこで replace() で個別に置き換えています。長音記号「ー」をマイナスのつもりで打ってしまうこともあるので、あわせて拾っておくと親切です。
この関数を process() の .strip() の代わりに使えば、全角入力もそのまま変換できるようになります。
実験4: 起動直後にカーソルを入力欄へ置く
起動してすぐ数字を打ち始められるようにします。1行足すだけで、体感の使い勝手がはっきり変わる部類の改善です。
self.entry.focus_set()
_build_ui() の最後に書きます。すでに bind("<Return>", ...) でEnterキーを割り当ててあるので、これでマウスに触らずに「数字を打つ → Enter → 結果を見る」が完結します。何度も変換したいときの快適さが段違いです。
実験5: 変換の履歴を残す
いまは変換するたびに前の結果が消えます。消さずに積み上げると、複数の温度を見比べられるツールになります。
def _show_result(self, text):
self.result_text.config(state=tk.NORMAL)
self.result_text.insert(tk.END, text + "\n\n") # 消さずに末尾へ追加
self.result_text.see(tk.END) # 最新行までスクロール
self.result_text.config(state=tk.DISABLED)
delete() の行を消し、insert() の位置を "1.0"(先頭)から tk.END(末尾)に変えただけです。see(tk.END) は追加した行が見えるところまでスクロールするメソッドで、これがないと履歴が増えたときに最新の結果が画面の外に隠れてしまいます。
なお完成コードにはスクロールバーを置いてありますが、Text を先に pack(fill=tk.BOTH, expand=True) で配置しているため、あとから pack() するスクロールバーには残った余白しか回ってきません(pack() は書いた順に場所を取ります)。既定サイズを 500×812 にしてあるいまは、結果欄の下に残る余白の右端に短いスクロールバーが出ますが、結果欄の高さいっぱいには伸びません。履歴を積み上げて実際にスクロールさせたい場合は、配置の順序を入れ替えてスクロールバーに先に場所を取らせてください。
self.result_text = tk.Text(
result_frame,
font=("Noto Sans JP", 11),
bg="white", relief=tk.FLAT,
state=tk.DISABLED
) # ここでは pack しない
scrollbar = ttk.Scrollbar(result_frame, command=self.result_text.yview)
self.result_text.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 先に右端を確保する
self.result_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 残りを Text が使う
Windows 11 / Python 3.12.10 で既定サイズ(500×812)のままスクロールバーの大きさを測ったところ、入れ替える前は幅 17 ピクセル × 高さ 51 ピクセル(結果欄の下の余白に出る短いバー)でしたが、入れ替えたあとは幅 17 ピクセル × 高さ 559 ピクセルになり、結果欄の右端いっぱいに並びました。pack() の順序だけでスクロールバーが取れる場所が決まる、という分かりやすい例です。なお既定サイズが 500×400 だった頃は、入れ替える前のスクロールバーは幅 1 ピクセルで画面に出ていませんでした(2026-09-09 に既定サイズを 500×812 へ広げて解消)。
8. うまく動かないときの症状別チェック
入力を受け取るアプリは、エラーが出るパターンと「エラーは出ないのに結果がおかしい」パターンの両方があります。まず症状から当たりを付けてください。
| 症状 | まず疑うところ |
|---|---|
could not convert string to float | 空欄・全角のピリオド・カンマ区切り |
| 日本語入力のまま小数を打つと弾かれる | 全角の小数点(対処) |
'NoneType' object has no attribute 'get' | 作成と配置を1行にまとめている |
足し算のはずが数字が並ぶ/TypeError | float() の呼び忘れ |
結果が -459.66999999999996 になる | 書式指定 :.2f がない |
nan や inf が表示される | float() はこの2つを通してしまう |
| 単位を変えても結果が変わらない | 選択値の比較文字列のずれ |
| Enterキーで反応しない | <Return> と <Enter> の取り違え |
❌ ValueError: could not convert string to float
原因: float() に渡した文字列が数値として読めません。GUIアプリで最も多いのは空欄のまま変換ボタンを押したケースで、このときメッセージの末尾は : '' という空のクォートになります。ここが空なら「入力されていない」と即断できます。
対処: 3段構えにします。①空欄は float() に渡す前に if not value: で止める、②それ以外の失敗は except ValueError で受けて messagebox で知らせる、③何を直せばいいかをメッセージに書く。完成コードの process() がこの形です。
value = self.entry.get().strip()
if not value:
messagebox.showwarning("警告", "値を入力してください")
return
try:
temp = float(value)
except ValueError:
messagebox.showerror("エラー", f"数値として読めません: {value}")
return
エラーメッセージに実際に入力された文字列を含めるのが要点です。「不正な値です」とだけ出すより、「数値として読めません: 25,5」と出したほうが、利用者は自分でカンマに気づけます。他のエラーメッセージの逆引きはPythonエラー一覧&解決法にまとめています。
❌ 日本語入力のまま小数を打つと弾かれる(全角の小数点)
原因: 全角の小数点です。Python 3.12.10 で確かめたところ、全角数字の "25" は 25.0 に変換できるのに、全角のピリオドを含む "12.5" は ValueError になりました。数字が通るぶん、原因が小数点だと気づきにくい厄介な失敗です。
対処: float() に渡す前に unicodedata.normalize("NFKC", ...) で正規化します。ただし数学記号のマイナス(U+2212)は NFKC でも変換されないため、replace() の併用が要ります。具体的なコードは7章の実験3に置きました。
正規化を入れないなら、せめて入力欄の近くに「半角数字で入力してください」と添えるだけでも失敗はかなり減ります。
❌ AttributeError: 'NoneType' object has no attribute 'get'
原因: ウィジェットの作成と配置を1行にまとめています。pack()・grid()・place() はいずれも None を返すので、変数には入力欄ではなく None が入ります。
# これはダメ(entry の中身は None になる)
entry = ttk.Entry(root).pack()
entry.get() # AttributeError: 'NoneType' object has no attribute 'get'
# 正しい書き方
entry = ttk.Entry(root)
entry.pack()
対処: あとから get() や config() を呼びたいウィジェットは、必ず作成と配置を2行に分けてください。逆に、二度と触らない飾りのラベルなどは1行にまとめても実害はありません。AttributeError 全般の読み方はエラー解決ページのAttributeErrorの章にあります。
❌ 文字列のまま計算して TypeError になる
原因: float() を通していません。get() が返すのは文字列なので、数値と混ぜて計算しようとすると型の不一致で止まります。
value = entry.get() # "25"
value + 5 # TypeError: can only concatenate str (not "int") to str
value * 3 # エラーにならず "252525"(文字列が3回繰り返される)
float(value) + 5 # 30.0 ← 正しい
対処: 計算に使う前に必ず float() か int() で変換します。注意すべきは value * 3 の行です。掛け算は文字列の繰り返しとして成立してしまうため、エラーが出ないまま "252525" という結果が出ます。「エラーは出ないのに数字がおかしい」ときは、この型の取り違えを疑ってください。温度のように小数を扱う場面では、int() ではなく float() を使います(int("25.5") は ValueError になります)。
❌ 結果が -459.66999999999996 のようになる
原因: 浮動小数点数の丸め誤差です。計算式は正しく、Pythonの不具合でもありません。コンピュータは10進数の小数を2進数で近似して保持しているため、0.1 + 0.2 が 0.30000000000000004 になるのと同じ現象です。手元で確認した例を挙げます。
print(-273.15 * 9 / 5 + 32) # -459.66999999999996
print(-40 + 273.15) # 233.14999999999998
print(36.6 * 9 / 5 + 32) # 97.88000000000001
print(0.1 + 0.2) # 0.30000000000000004
対処: 表示するときに桁を決めます。f"{value:.2f}" で小数点以下2桁に整えれば、上の値はそれぞれ -459.67・233.15・97.88 と表示されます。計算の途中で丸めず、表示の直前だけ丸めるのが原則です。途中で丸めると誤差が積み重なります。
なお、比較にも注意が必要です。変換して戻した値が元と厳密に一致するとは限らないため、等号ではなく math.isclose() を使うのが安全です。2章の検算スクリプトがこの形になっています。
❌ nan や inf を入れると絶対零度チェックをすり抜ける
原因: float() は "nan" と "inf" を、例外を出さずにそのまま受け付けます。公式ドキュメントも、無限大やNaNを表す文字列を渡せること、大文字小文字は区別されないことを明記しています。問題はそのあとで、nan は大小比較(<・>・<=・>=)も == もすべて False を返すという性質を持っています(!= だけが True で、これが x != x で nan を見分けられる理由です)。
temp = float("nan")
print(temp < -273.15) # False ← 弾けない
print(temp > -273.15) # False ← こちらも False
temp = float("inf")
print(temp < -273.15) # False ← やはり通ってしまう
つまり if celsius < -273.15: だけでは nan も inf も素通りし、結果欄に「nan °C」と表示されます。実際に math.isfinite() の行を外した状態で nan と入力し、この挙動を再現しました。
対処: math.isfinite() で「有限の数値かどうか」を先に確認します。完成コードでは float() の直後にこの1行を置いています。
import math
temp = float(value)
if not math.isfinite(temp):
raise ValueError("有限の数値を入力してください")
利用者がわざわざ nan と打つ場面は多くありませんが、「範囲チェックを書いたのに通り抜ける値がある」という事実自体を知っておく価値があります。入力を数値に変える処理では、この落とし穴はどこでも同じように現れます。
❌ 単位を変えても結果が変わらない
原因: 選択された値と、if で比較している文字列が一致していません。values=["摂氏 (°C)", ...] と登録した文字列を、比較側で "摂氏" や "摂氏(°C)"(空白なし)と書いてしまうと、どの分岐にも入らず else のケルビン扱いになります。
対処: まず何が返ってきているかを確認します。print(repr(self.unit_var.get())) を _calculate() の先頭に一時的に入れれば一目瞭然です。そのうえで、表示文字列そのもので分岐するのをやめ、次のように辞書で対応付けると書き間違いが起きにくくなります。
UNITS = {
"摂氏 (°C)": "C",
"華氏 (°F)": "F",
"ケルビン (K)": "K",
}
code = UNITS[self.unit_var.get()] # "C" / "F" / "K"
また、textvariable も current() も指定せずに Combobox を作ると、初期状態の get() は空文字列を返します。完成コードは StringVar(value="摂氏 (°C)") で初期値を与えているため、この問題は起きません。
❌ Enterキーを押しても変換されない
原因: イベント名の取り違えが定番です。キーボードのEnterキーは <Return> で、<Enter> は「マウスカーソルがウィジェットに入った」という全く別のイベントです。<Enter> に割り当てると、キーではなくマウスを乗せたときに反応します。
entry.bind("<Return>", lambda e: process()) # 正しい(Enterキー)
entry.bind("<Enter>", lambda e: process()) # マウスが乗ったときに動く
対処: <Return> に直したうえで、lambda e: と引数を1つ受け取っているか確認してください。bind() で呼ばれる関数にはイベント情報のオブジェクトが必ず1つ渡されるため、引数なしの関数を渡すと呼び出しに失敗します。手元で試したところ TypeError: no_arg() takes 0 positional arguments but 1 was given という内容になり、しかもこのメッセージは画面ではなくターミナル側にだけ出ました。テンキーのEnterも拾いたい場合は <KP_Enter> を追加で割り当てます。
それでも反応しないときは、入力欄にフォーカスが当たっているかを疑ってください。実験4の focus_set() を入れておくと確実です。
出典: Python公式ドキュメント 組み込み関数 float()/同 tkinter(いずれも2026年8月16日確認)
9. 練習問題
手を動かして初めて身につきます。ヒントだけ添えるので、答えを見る前に自分で書いてみてください。
-
課題1: 変換の向きを1つ増やす
ランキン度(°R)に対応させましょう。絶対零度を0とする華氏系の単位です。NISTの換算式は華氏からケルビンが
T/K = (t/°F + 459.67)/1.8、ランキン度からケルビンがT/K = (T/°R)/1.8なので、この2つから °R と °F の関係が導けます。
ヒント: 「入り口2本・出口2本」の設計なので、追加するのは摂氏との相互変換2本と、Combobox の選択肢1つだけです。(出典: NIST SP 811 Appendix B.8・2026年8月16日確認) -
課題2: 入力の範囲に注意書きを出す
絶対零度に近い値(たとえば -270 °C 以下)を入力したとき、エラーにはせず「極低温の領域です」と補足を添えて表示しましょう。
ヒント: 変換結果の文字列を組み立てる直前にif celsius <= -270:で分岐し、メッセージを1行足すだけです。 -
課題3: 結果をファイルに保存する
「保存」ボタンを追加し、これまでの変換結果をテキストファイルに書き出せるようにしましょう。
ヒント:from tkinter import filedialogのあとfiledialog.asksaveasfilename()で保存先を選べます。取得した内容はself.result_text.get("1.0", tk.END)で読めます。 -
課題4: 変換関数のテストを書く
2章の検算スクリプトを参考に、自分が追加した変換式にも
assertによるチェックを書きましょう。
ヒント: 答えが決まっている点(水の凝固点・沸点・絶対零度)を選ぶと、期待値を自分で計算せずに済みます。比較にはmath.isclose()を使ってください。
10. よくある質問
Q. 摂氏から華氏、華氏から摂氏の式を教えてください。
摂氏から華氏は °F = °C * 9 / 5 + 32、華氏から摂氏は °C = (°F - 32) * 5 / 9 です。9 / 5 は 1.8、5 / 9 は 1 / 1.8 と同じなので、どちらの書き方でも結果は変わりません。書けたら -40 を入れてみてください。摂氏と華氏の数値が一致する唯一の点なので、式の検算に使えます。
Q. ケルビンへの変換で 273 ではなく 273.15 を使うのはなぜですか?
NISTは「摂氏0度は 273.15 K である」と示しています。273 で計算すると 0.15 度ぶんずれるため、絶対零度が -273.00 と表示されるなど、境界の検算が合わなくなります。学習用でも 273.15 を使うことをおすすめします。
Q. int() ではなく float() を使うのはなぜですか?
温度は 36.5 度のように小数で入力されるからです。int("36.5") は ValueError になり、小数点を含む入力がすべて弾かれてしまいます。整数だけを扱いたい場面でも、いったん float() で受けてから丸めるほうが利用者にとっては親切です。
Q. entry.get() が空文字列しか返しません。
まず entry = ttk.Entry(root).pack() のように作成と配置を1行にまとめていないか確認してください(この場合は AttributeError になります)。次に、値を読むタイミングを確認します。root.mainloop() の前に get() を書くと、利用者が入力する前の空の状態を読んでしまいます。get() はボタンやキーのイベントで呼ばれる関数の中に置いてください。
Q. 入力欄に数字しか打てないようにできますか?
できます。root.register() で検証関数を登録し、Entry の validate="key" と validatecommand に渡します。具体的なコードは7章の実験2にあります。ただしこの方法でも "nan" のようなすり抜けは起きるため、計算する直前のチェックは残しておいてください。
Q. GUIではなくコマンドラインだけで作りたいのですが。
2章に input() を使ったコマンドライン版を掲載しています。計算部分はGUI版とほぼ同じで、違いは値の受け取り方と表示方法だけです。処理の中身と画面まわりを分けておくと、片方だけ差し替えられます。
Q. 表示される桁数を変えたいです。
f"{celsius:.2f}" の 2 を書き換えます。:.1f なら小数第1位まで、:.0f なら整数に丸められます。丸めるのは表示の直前だけにして、計算の途中では丸めないでください。誤差が積み重なります。
Q. 絶対零度より低い値をわざわざ弾く必要はありますか?
学習目的なら必須ではありませんが、入れておくと「入力チェックを書く」練習になります。実際のアプリでは、物理的にありえない値をそのまま計算して表示するより、理由を添えて断ったほうが親切です。同じ考え方は、年齢にマイナスを入れさせない、体重に0を入れさせないといった場面でそのまま使えます。
Q. 温度以外の単位変換にも応用できますか?
できます。「いったん基準の単位にそろえてから出力する」という設計はそのまま使えます。長さならメートル、重さならグラムを基準に決めるだけです。実装例は長さ単位変換や重さ単位変換にあります。ただし為替のようにレートが変動するものは、係数を固定値で持てない点が温度と違います。
Q. コードは動いたけれど、クラスの書き方がまだ分かりません。
その段階で問題ありません。まずは「self. を付けた変数は、同じクラスのどのメソッドからも見える」という一点だけ押さえてください。6章のようにクラスなしの形から始めて、変数が増えて見通しが悪くなったタイミングでクラスに移すと、必要性が実感として分かります。
11. 次の一歩
「入力を受け取る → 数値に変える → 計算する → 表示する」という流れは、これから作るアプリのほとんどで再登場します。次はこの型を保ったまま、入力や表示を1つだけ複雑にするのがおすすめです。
| 次に作るもの | ここで新しく身につく要素 |
|---|---|
| BMI計算アプリ | 入力欄が2つになったときの取り回しと、結果の判定分岐 |
| 長さ単位変換 | 単位が増えても破綻しない、辞書を使った係数の持ち方 |
| tkinterでシンプル電卓を作る | grid() によるボタンの表配置と、入力の組み立て |
| 通貨換算アプリ | 固定値ではない「変動するレート」の扱い方 |
扱う対象を「1件の値」から「複数件の一覧」へ広げたくなったら、次は Listbox の出番です。入力欄なら get() で文字列がそのまま返りますが、一覧では「いま選ばれている行の番号」を受け取ってから元データを引き直す手順が要ります。ToDoリストアプリで選択中の項目を正しく取り出す型を先に押さえておくと、この先どのアプリでも行番号のずれで詰まらずに済みます。
入力欄の扱いに慣れたいならBMI計算アプリ、レイアウトを学びたいならシンプル電卓が近道です。どちらもこのページで書いた try / except の型がそのまま使えます。
tkinter のウィジェットをひととおり見渡したい場合はtkinterで最初のウィンドウを表示するの3層構造の説明が土台になります。GUI以外のライブラリも含めた全体像はPythonライブラリ一覧に、独学の順番を1冊で固めたい場合はPython学習本の比較にまとめてあります。
写経中に赤いエラー文が出たら、Pythonエラー一覧&解決法(英語メッセージ逆引き)で原因と直し方をすぐ確認できます。
写経しながら「なぜこう書くのか」が気になり始めたら、入門書を1冊通して読むと断片的な知識がつながります。Python入門書のおすすめ2冊(当サイトの参考書ランキング総合1〜2位)で、独学者向けの最初の1冊を比較しています。