初心者向け No.47

進数変換アプリ

10進数・2進数・8進数・16進数を相互変換するツール。Pythonの進数変換組み込み関数を学びます。

🎯 難易度: ★☆☆ 📦 ライブラリ: tkinter(標準ライブラリ) ⏱️ 制作時間: 30〜90分

1. アプリ概要

10進数・2進数・8進数・16進数を相互変換するツール。Pythonの進数変換組み込み関数を学びます。

このアプリは変換カテゴリに分類される実践的なGUIアプリです。使用ライブラリは tkinter(標準ライブラリ) で、難易度は ★☆☆ です。

このアプリは「変換ツール」カテゴリです。形式変換ツールは入力・処理・出力の三層を明示的に書く好材料で、エラー入力の扱いやバリデーションの基本を体験できます。tkinter(標準ライブラリ) を活かして実装するこの構造は、他のアプリにも応用が効きます。

コピー&実行で動作確認 → コード解説で構造理解 → カスタマイズで応用、の 3 ステップで進めると、短時間で本質を押さえられます。

アプリを完成させた後は、自分の使い方に合わせて改造するのが学びを定着させる近道です。カスタマイズ章のアイデアを足がかりに、独自機能を一つ追加してみてください。

進数変換アプリ 実行画面(Windows)
実行画面(Windows)
進数変換アプリ 実行画面(Linux Mint)
実行画面(Linux Mint)

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. 完全なソースコード

💡
コードのコピー方法

右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。

追加インストール不要(標準ライブラリのみ使用)
app47.py
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.Entryttk.Radiobutton(ループで生成)・tk.Button(ループで生成) で構成しています。見出し付きの枠(LabelFrame)は「入力」「変換結果」のラベルでエリアを区切っています。

※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。

イベント処理とボタンの接続

ボタンはループ内で command=lambda k=key: self._copy(k) の形で生成しています。既定引数でループ変数を固定するのがポイントで、これを書かないと全ボタンが最後の値で動いてしまいます。また bind("<Return>", ...) から convert()bind("<KeyRelease>", ...) から convert() を呼べるようにイベントも登録しています。

※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。

例外処理

try-except で ValueError を捕捉しています。

※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。

6. ステップバイステップガイド

このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。

  1. 1
    ファイルを作成する

    新しいファイルを作成して app47.py と保存します。使うのは tkinter だけなので、追加インストールは不要です。

  2. 2
    クラスの骨格を作る

    App47 クラスを定義し、__init__ と、末尾の root = tk.Tk()mainloop() の最小構成を書いて、まず空のウィンドウが出ることを確認します。

  3. 3
    ウィンドウを設定する

    title("進数変換アプリ")geometry("551x380")configure(bg="#f8f9fc")minsize(551, 380) をコンストラクタで設定します。

  4. 4
    画面部品を並べる

    _build_ui() の中で、tk.Frame×3(ほかにループでも生成)・tk.Label×3(ほかにループでも生成)・ttk.LabelFrame×2・ttk.Entryttk.Radiobutton(ループで生成)・tk.Button(ループで生成) を作って配置します(掲載コードと同じ並び順で書くとレイアウトが一致します)。まず表示だけ確認しましょう。

  5. 5
    イベントを接続する

    ループ生成のボタンは command=lambda k=key: self._copy(k) の形で接続します。bind("<Return>", ...)bind("<KeyRelease>", ...) の登録も忘れずに。

  6. 6
    中心になるメソッドを実装する

    アプリの本体である convert()(22行)・_copy()(4行) を実装します。

  7. 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. 課題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:コピーできる値がないときに知らせる

    いまは起動した直後に「コピー」を押しても画面は何も変わりません。押した結果が分かるように、コピーできる値がないときは知らせを出しましょう。

    期待結果:起動直後(入力欄が空の状態)で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. 課題3:保存機能の追加

    入力値や計算結果をファイルに保存する機能を追加しましょう。jsonやcsvモジュールを使います。

🚀
次に挑戦するアプリ

このアプリをマスターしたら、次のアプリにも挑戦してみましょう。

🐛
エラーが出て動かないときは

写経中に赤いエラー文が出たら、Pythonエラー一覧&解決法(英語メッセージ逆引き)で原因と直し方をすぐ確認できます。

📖
独学を1冊で体系化するなら

写経しながら「なぜこう書くのか」が気になり始めたら、入門書を1冊通して読むと断片的な知識がつながります。Python入門書のおすすめ2冊(当サイトの参考書ランキング総合1〜2位)で、独学者向けの最初の1冊を比較しています。