進数変換アプリ
10進数・2進数・8進数・16進数を相互変換するツール。Pythonの進数変換組み込み関数を学びます。
1. アプリ概要
10進数・2進数・8進数・16進数を相互変換するツール。Pythonの進数変換組み込み関数を学びます。
このアプリは変換カテゴリに分類される実践的なGUIアプリです。使用ライブラリは tkinter(標準ライブラリ) で、難易度は ★☆☆ です。
このアプリは「変換ツール」カテゴリです。形式変換ツールは入力・処理・出力の三層を明示的に書く好材料で、エラー入力の扱いやバリデーションの基本を体験できます。tkinter(標準ライブラリ) を活かして実装するこの構造は、他のアプリにも応用が効きます。
コピー&実行で動作確認 → コード解説で構造理解 → カスタマイズで応用、の 3 ステップで進めると、短時間で本質を押さえられます。
アプリを完成させた後は、自分の使い方に合わせて改造するのが学びを定着させる近道です。カスタマイズ章のアイデアを足がかりに、独自機能を一つ追加してみてください。
2. 機能一覧
- ループで生成するボタン群を
_copy()に接続 - キー
<Return>からconvert()を実行 - キー
<KeyRelease>からconvert()を実行 - ウィンドウはタイトル「進数変換アプリ」・サイズ 551x380 で起動
3. 事前準備・環境
Python 3.10 以上 / Windows 11(実機)で起動・基本操作を確認 / Linux Mint 22.3(仮想マシン)で起動・画面表示を確認(macOS は未検証)
Windows 11(実機)で起動と基本操作を確認しています(全機能の網羅テストではありません)。Linux Mint 22.3(仮想マシン)では起動と画面表示を確認しました。macOS は標準ライブラリの範囲で動作する想定ですが、未検証です。
- Python 3.10 以上
- OS: Windows 11(実機で起動・基本操作を確認)・Linux Mint 22.3(仮想マシンで起動・画面表示を確認)
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
import tkinter as tk
from tkinter import ttk, messagebox
class App47:
"""進数変換アプリ"""
def __init__(self, root):
self.root = root
self.root.title("進数変換アプリ")
self.root.geometry("551x380")
self.root.minsize(551, 380)
self.root.configure(bg="#f8f9fc")
self._build_ui()
def _build_ui(self):
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=24, pady=16)
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", pady=4)
self.entry = ttk.Entry(input_frame, width=22, font=("Courier New", 13))
self.entry.grid(row=0, column=1, padx=8, sticky="ew")
self.entry.bind("<Return>", lambda e: self.convert())
self.entry.bind("<KeyRelease>", lambda e: self.convert())
tk.Label(input_frame, text="入力基数:").grid(row=1, column=0, sticky="w", pady=4)
self.base_var = tk.IntVar(value=10)
bg_color = self.root.cget("bg")
base_frame = tk.Frame(input_frame, bg=bg_color)
base_frame.grid(row=1, column=1, sticky="w", padx=8)
for val, label in [(10, "10進数"), (2, "2進数"), (8, "8進数"), (16, "16進数")]:
ttk.Radiobutton(base_frame, text=label, variable=self.base_var,
value=val, command=self.convert).pack(side=tk.LEFT, padx=4)
input_frame.columnconfigure(1, weight=1)
# 結果
result_frame = ttk.LabelFrame(main_frame, text="変換結果", padding=12)
result_frame.pack(fill=tk.BOTH, expand=True)
self.result_labels = {}
for row, (label, key) in enumerate([
("10進数 (DEC):", "dec"),
("2進数 (BIN):", "bin"),
("8進数 (OCT):", "oct"),
("16進数 (HEX):", "hex"),
]):
tk.Label(result_frame, text=label, font=("Courier New", 11),
bg=bg_color, width=16, anchor="w").grid(
row=row, column=0, sticky="w", pady=4)
val_frame = tk.Frame(result_frame, bg=bg_color)
val_frame.grid(row=row, column=1, sticky="ew", padx=8)
lbl = tk.Label(val_frame, text="--",
font=("Courier New", 12, "bold"),
bg="white", fg="#3776ab",
relief=tk.FLAT, anchor="w",
padx=6, pady=2, width=24)
lbl.pack(side=tk.LEFT)
tk.Button(val_frame, text="コピー", font=("Arial", 8),
bg="#e8eaf0", relief=tk.FLAT,
command=lambda k=key: self._copy(k)).pack(side=tk.LEFT, padx=4)
self.result_labels[key] = lbl
result_frame.columnconfigure(1, weight=1)
def convert(self):
text = self.entry.get().strip()
if not text:
for lbl in self.result_labels.values():
lbl.config(text="--", fg="#3776ab")
return
base = self.base_var.get()
try:
n = int(text, base)
except ValueError:
for lbl in self.result_labels.values():
lbl.config(text="入力エラー", fg="#e74c3c")
return
self.result_labels["dec"].config(text=str(n), fg="#3776ab")
self.result_labels["bin"].config(text=bin(n)[2:], fg="#3776ab")
self.result_labels["oct"].config(text=oct(n)[2:], fg="#3776ab")
self.result_labels["hex"].config(text=hex(n)[2:].upper(), fg="#3776ab")
self._last_values = {
"dec": str(n), "bin": bin(n)[2:],
"oct": oct(n)[2:], "hex": hex(n)[2:].upper()
}
def _copy(self, key):
if hasattr(self, "_last_values"):
self.root.clipboard_clear()
self.root.clipboard_append(self._last_values.get(key, ""))
if __name__ == "__main__":
root = tk.Tk()
app = App47(root)
root.mainloop()
5. コード解説
進数変換アプリのコードを、実際に書かれている実装に沿って解説します。
クラス設計とコンストラクタ
App47 クラスにアプリの全機能をまとめています(メソッド4個・全106行)。__init__ ではタイトル「進数変換アプリ」とウィンドウサイズ 551x380 を設定します。最後に _build_ui() で画面を組み立てます。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
ウィジェット構成
画面は tk.Frame×3(ほかにループでも生成)・tk.Label×3(ほかにループでも生成)・ttk.LabelFrame×2・ttk.Entry・ttk.Radiobutton(ループで生成)・tk.Button(ループで生成) で構成しています。見出し付きの枠(LabelFrame)は「入力」「変換結果」のラベルでエリアを区切っています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
イベント処理とボタンの接続
ボタンはループ内で command=lambda k=key: self._copy(k) の形で生成しています。既定引数でループ変数を固定するのがポイントで、これを書かないと全ボタンが最後の値で動いてしまいます。また bind("<Return>", ...) から convert()、bind("<KeyRelease>", ...) から convert() を呼べるようにイベントも登録しています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1ファイルを作成する
新しいファイルを作成して app47.py と保存します。使うのは
tkinterだけなので、追加インストールは不要です。 -
2クラスの骨格を作る
App47クラスを定義し、__init__と、末尾のroot = tk.Tk()~mainloop()の最小構成を書いて、まず空のウィンドウが出ることを確認します。 -
3ウィンドウを設定する
title("進数変換アプリ")・geometry("551x380")・configure(bg="#f8f9fc")・minsize(551, 380)をコンストラクタで設定します。 -
4画面部品を並べる
_build_ui()の中で、tk.Frame×3(ほかにループでも生成)・tk.Label×3(ほかにループでも生成)・ttk.LabelFrame×2・ttk.Entry・ttk.Radiobutton(ループで生成)・tk.Button(ループで生成) を作って配置します(掲載コードと同じ並び順で書くとレイアウトが一致します)。まず表示だけ確認しましょう。 -
5イベントを接続する
ループ生成のボタンは
command=lambda k=key: self._copy(k)の形で接続します。bind("<Return>", ...)・bind("<KeyRelease>", ...)の登録も忘れずに。 -
6中心になるメソッドを実装する
アプリの本体である
convert()(22行)・_copy()(4行) を実装します。 -
7動作確認する
python app47.pyで起動し、各ボタンが反応すること・キー操作(<Return>・<KeyRelease>)が効くことを確認します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。少しずつ機能を追加することで、Pythonのスキルが飛躍的に向上します。
💡 ダークモードを追加する
bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。
💡 結果をファイルに保存する
このコードに保存処理はないため、表示中の数値や結果はアプリを閉じると消えます。テキストファイルへ書き出して、次回起動時に読み込む機能を追加してみましょう。
💡 入力履歴機能
以前の入力値を覚えておいてComboboxのドロップダウンで再選択できる履歴機能を追加しましょう。
8. よくある問題と解決法
❌ 文字のフォントが崩れる・見た目が違う
原因:このコードは font=("Noto Sans JP", ...) のようにフォント名を直接指定しています。環境にこのフォントが入っていないと、OS が代わりのフォントで表示するため、見本と見た目が変わることがあります。
解決法:動作には支障ありません。気になる場合は font 引数の名前を自分の OS に入っているフォントへ書き換えるか、font 引数を省略してください。
❌ 写経したら全部のボタンが同じ動きになる
原因:ループでボタンを作るとき、このコードは command=lambda k=key: self._copy(k) のように既定引数でループ変数を固定しています。これを省略してループ変数を直接書くと、ループ終了後の最後の値が全ボタンで共有されてしまいます(Python のクロージャの有名な罠です)。
解決法:掲載コードのとおり、lambda の既定引数でその時点の値を渡してください。
❌ <Return> キーを押しても反応しない
原因:bind("<Return>", ...) はウィジェット単位で登録されるため、ウィンドウ(またはバインド先)にキーボードフォーカスが無いとイベントが届きません。
解決法:一度ウィンドウ内をクリックしてフォーカスを与えてから、キーを押してください。
❌ ウィンドウの大きさが画面に合わない
原因:geometry("551x380") の固定サイズで起動するためです。
解決法:geometry() と minsize() の両方の数値を書き換えて起動サイズを調整してください。なお、このコードにはウィンドウサイズを固定する設定(resizable の指定)が無いため、ウィンドウの端をドラッグしたサイズ変更は既定どおり可能です。ただし minsize(551, 380) を指定しているため、起動時の大きさより小さくは縮められません(縮めると画面部品が表示されなくなるのを防ぐためです)。
9. 練習問題
アプリの理解を深めるための練習問題です。気になるものから挑戦してみてください(課題3は定型の発展課題です)。どちらの課題も新しいライブラリは要りません(起動して数値を打ち込むだけで確かめられます)。
-
課題1:マイナスの数を正しく表示する
入力欄に
-255と入れると、2進数の欄がb11111111という見慣れない文字列になります。マイナスでも読める表記に直しましょう。期待結果:入力基数を「10進数」にして
-255と入れると、2進数が-11111111・8進数が-377・16進数が-FFになる。いまは 2進数がb11111111・8進数がo377・16進数がXFFと出る合格条件
0を入れたときは2進数・8進数・16進数がすべて0のまま- 正の数の表示は今までどおり(
255なら2進数11111111・16進数FF) - 「コピー」ボタンで貼り付く文字も画面の表示と同じになる
ヒント①(どこを触るか): 触るのは1か所です。
convert()の後半、self.result_labels["dec"].config(text=str(n), fg="#3776ab")からself._last_valuesを組み立てているところまでのかたまりです。
ヒント②(使うもの): 符号をsign = "-" if n < 0 else ""で作り、bin(abs(n))[2:]のように絶対値を渡してから頭にsignを足します。self._last_valuesにも同じ文字列を入れると、コピーの中身も画面と揃います。
つまずきやすい点:bin(n)[2:]の[2:]は先頭の0bを落とすための書き方です。bin(-255)は-0b11111111なので、落ちるのは-0の2文字になり、頭にbが残ります。[3:]に変えるとbは消えますが、bin(-255)は-0b11111111なのでマイナス記号まで一緒に落ちて11111111になり、正の数と見分けが付かなくなります。bin(255)のほうは0b11111111なので先頭が1桁消えて1111111になります。 -
課題2:コピーできる値がないときに知らせる
いまは起動した直後に「コピー」を押しても画面は何も変わりません。押した結果が分かるように、コピーできる値がないときは知らせを出しましょう。
期待結果:起動直後(入力欄が空の状態)で2進数の「コピー」を押すと「コピーできる値がありません」というダイアログが出る。いまは押しても何も起きない
合格条件
- 数値を入れて変換したあとは、今までどおりクリップボードへコピーできる
- 入力欄の数字をすべて消したあとに押すと、古い値ではなく知らせが出る
- 4つの進数のどのボタンでも同じように動く
ヒント①(どこを触るか): 触るのは2か所です。
convert()の「入力が空のとき」のreturnの手前と、_copy()の中です。
ヒント②(使うもの): 空のときにself._last_values = {}を入れておき、_copy()の先頭でvalues = getattr(self, "_last_values", {})を取り出します。空ならmessagebox.showinfo("情報", "コピーできる値がありません")を出してreturnします。messageboxは2行目のfrom tkinter import ttk, messageboxで読み込み済みです(いまのコードでは一度も使われていません)。
つまずきやすい点: いまの_copy()はhasattr(self, "_last_values")で「一度でも変換に成功したか」しか見ていません。そのため入力欄を空にしても古い値が残り、押すと前の数字が貼り付きます。hasattrのままにして空かどうかの判定を足さないと、この点は直りません。 -
課題3:保存機能の追加
入力値や計算結果をファイルに保存する機能を追加しましょう。jsonやcsvモジュールを使います。
写経中に赤いエラー文が出たら、Pythonエラー一覧&解決法(英語メッセージ逆引き)で原因と直し方をすぐ確認できます。
写経しながら「なぜこう書くのか」が気になり始めたら、入門書を1冊通して読むと断片的な知識がつながります。Python入門書のおすすめ2冊(当サイトの参考書ランキング総合1〜2位)で、独学者向けの最初の1冊を比較しています。