シンプル電卓
四則演算ができる電卓アプリです。Gridレイアウトとボタン生成の効率的な実装を学びます。
1. アプリ概要
電卓はGUI学習の定番プロジェクトです。多数のボタンを整列させるGridレイアウト、計算式の文字列処理、エラーハンドリングなど、実践的なGUIプログラミングの要素が凝縮されています。
このアプリでは、数字ボタン(0〜9)・演算子ボタン(+−×÷)・機能ボタン(C、=、.)を組み合わせた本格的な電卓を実装します。ボタンを一つひとつ手動で作る代わりに、リストと繰り返し処理を使って効率的に生成する方法も学べます。
計算処理には Python の組み込み関数 eval() を使います。eval()は文字列をPythonの式として評価する強力な機能ですが、ユーザー入力をそのままeval()に渡すのはセキュリティリスクがあります。このアプリでは安全な数式のみを受け付ける検証も実装します。
また、ZeroDivisionError(ゼロ除算)や不正な式の入力など、現実のアプリで必ず考慮すべきエラーハンドリングについても詳しく解説します。
2. 機能一覧
- 数字と小数点の入力
- 四則演算(加算・減算・乗算・除算)
- パーセント計算
- バックスペース(1文字削除)
- クリア(全消し)
- キーボード入力対応
- エラーハンドリング(ゼロ除算・不正式)
3. 事前準備・環境
Python 3.10 以上 / Windows・Mac・Linux すべて対応
tkinterのみ使用します。Pythonがインストールされていれば追加作業は不要です。
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
import tkinter as tk
class Calculator:
"""電卓アプリのメインクラス"""
# ボタンの定義: (テキスト, 行, 列, colspan, bg色)
BUTTONS = [
("C", 1, 0, 1, "#e74c3c"), ("←", 1, 1, 1, "#e67e22"),
("%", 1, 2, 1, "#95a5a6"), ("÷", 1, 3, 1, "#3498db"),
("7", 2, 0, 1, "#ecf0f1"), ("8", 2, 1, 1, "#ecf0f1"),
("9", 2, 2, 1, "#ecf0f1"), ("×", 2, 3, 1, "#3498db"),
("4", 3, 0, 1, "#ecf0f1"), ("5", 3, 1, 1, "#ecf0f1"),
("6", 3, 2, 1, "#ecf0f1"), ("−", 3, 3, 1, "#3498db"),
("1", 4, 0, 1, "#ecf0f1"), ("2", 4, 1, 1, "#ecf0f1"),
("3", 4, 2, 1, "#ecf0f1"), ("+", 4, 3, 1, "#3498db"),
("0", 5, 0, 2, "#ecf0f1"), (".", 5, 2, 1, "#ecf0f1"),
("=", 5, 3, 1, "#2ecc71"),
]
def __init__(self, root):
self.root = root
self.root.title("電卓")
self.root.geometry("280x380")
self.root.resizable(False, False)
self.root.configure(bg="#2c3e50")
self.expression = ""
self._build_ui()
self._bind_keyboard()
def _build_ui(self):
"""UIを構築する"""
# 表示エリア
self.display_var = tk.StringVar(value="0")
display = tk.Label(
self.root,
textvariable=self.display_var,
font=("Arial", 28, "bold"),
bg="#34495e", fg="white",
anchor="e", padx=12,
height=2
)
display.grid(row=0, column=0, columnspan=4,
sticky="nsew", padx=4, pady=(4, 2))
# ボタンを生成
for (text, row, col, span, color) in self.BUTTONS:
fg = "white" if color != "#ecf0f1" else "#2c3e50"
btn = tk.Button(
self.root,
text=text,
font=("Arial", 16, "bold"),
bg=color, fg=fg,
activebackground=self._darken(color),
relief=tk.FLAT,
cursor="hand2",
command=lambda t=text: self.on_press(t)
)
btn.grid(
row=row, column=col, columnspan=span,
sticky="nsew", padx=2, pady=2
)
# グリッドの重み設定(均等に拡張)
for i in range(4):
self.root.columnconfigure(i, weight=1)
for i in range(6):
self.root.rowconfigure(i, weight=1)
def _darken(self, hex_color):
"""色を少し暗くする"""
mapping = {
"#ecf0f1": "#bdc3c7", "#3498db": "#2980b9",
"#e74c3c": "#c0392b", "#2ecc71": "#27ae60",
"#e67e22": "#d35400", "#95a5a6": "#7f8c8d",
}
return mapping.get(hex_color, hex_color)
def _bind_keyboard(self):
"""キーボード入力をバインドする"""
# Tkinter の bind には記号そのものではなく keysym 名を使う必要がある
key_map = {
"Key-0": "0", "Key-1": "1", "Key-2": "2", "Key-3": "3", "Key-4": "4",
"Key-5": "5", "Key-6": "6", "Key-7": "7", "Key-8": "8", "Key-9": "9",
"plus": "+", "minus": "−", "asterisk": "×", "slash": "÷",
"period": ".", "Return": "=", "BackSpace": "←", "Escape": "C",
}
for key, action in key_map.items():
self.root.bind(f"<{key}>", lambda e, a=action: self.on_press(a))
def on_press(self, char):
"""ボタン押下時の処理"""
if char == "C":
self.expression = ""
self.display_var.set("0")
elif char == "←":
self.expression = self.expression[:-1]
self.display_var.set(self.expression or "0")
elif char == "=":
self._calculate()
elif char == "%":
try:
self.expression = str(eval(self.expression) / 100)
self.display_var.set(self.expression)
except Exception:
self.display_var.set("Error")
self.expression = ""
else:
# 記号を実際の演算子に変換
op_map = {"×": "*", "÷": "/", "−": "-"}
self.expression += op_map.get(char, char)
self.display_var.set(self.expression)
def _calculate(self):
"""計算実行"""
try:
result = eval(self.expression)
# 整数の場合は.0を除去
if isinstance(result, float) and result.is_integer():
result = int(result)
self.expression = str(result)
self.display_var.set(self.expression)
except ZeroDivisionError:
self.display_var.set("0除算エラー")
self.expression = ""
except Exception:
self.display_var.set("エラー")
self.expression = ""
if __name__ == "__main__":
root = tk.Tk()
app = Calculator(root)
root.mainloop()
5. コード解説
電卓アプリはクラスを使って実装しています。クラスを使うことでデータ(expression等)とUI操作をまとめて管理できます。
クラス設計の考え方
Calculatorクラスに電卓の全機能をまとめています。__init__でウィンドウ設定・UI構築・キーバインドを初期化します。クラスを使うことでグローバル変数を避け、コードが整理されます。
class Calculator:
def __init__(self, root):
self.root = root
self.expression = "" # 入力式を保持
BTUTONSリスト:データドリブンなボタン生成
ボタンの情報をリストで定義し、ループで一括生成しています。これにより各ボタンを個別にコーディングする手間が省けます。タプルの中身は(テキスト、行、列、colspan、背景色)です。
for (text, row, col, span, color) in self.BUTTONS:
btn = tk.Button(root, text=text, ...)
btn.grid(row=row, column=col, columnspan=span)
lambdaによるコマンドの束縛
forループ内でcommandを設定する際、lambda t=text: self.on_press(t) のように書きます。t=text はデフォルト引数で現在のtextの値をキャプチャします。lambda: self.on_press(text) ではすべてのボタンが最後のtextを参照してしまうので注意が必要です。
# 正しい書き方(デフォルト引数でキャプチャ)
command=lambda t=text: self.on_press(t)
# 間違い(すべて最後の値になる)
command=lambda: self.on_press(text)
Gridレイアウトの使い方
grid() は行・列でウィジェットを格子状に配置します。columnspan=2 で複数列にまたがる配置ができます(0ボタンは2列分)。sticky="nsew" でセルいっぱいに広げます。columnconfigure(weight=1) で列を均等に拡張します。
btn.grid(row=5, column=0, columnspan=2, sticky="nsew")
self.root.columnconfigure(0, weight=1) # 均等拡張
eval()による式の評価とセキュリティ
eval()は文字列をPythonの式として評価します。"3+4*2" → 11 のように計算できます。ただし、eval()は任意のコードを実行できるため、外部からの入力を直接渡すのは危険です。このアプリでは数字と演算子のみを受け付けてeval()に渡しているため安全です。
# 安全な使用例(数値と演算子のみ)
result = eval("3+4*2") # → 11
# 危険な使用例(絶対にやってはいけない)
# user_input = input() # 悪意ある入力の可能性
# eval(user_input)
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1クラスの骨組みを作る
Calculatorクラスを定義し、__init__メソッドにウィンドウ設定を記述します。
-
2表示エリア(ディスプレイ)を作る
StringVarと紐付けたLabelで計算式・結果を表示するエリアを作ります。
-
3ボタンデータを定義する
BTUTONSリストにボタン情報(テキスト・位置・色)を定義します。
-
4ボタンを一括生成する
forループでBTUTONSを繰り返し、各ボタンウィジェットを生成・配置します。
-
5on_press()メソッドを実装する
各ボタンが押されたときの処理(数字追加・クリア・計算)を実装します。
-
6キーボード入力を追加する
bind()でキーボードの数字・演算子キーを電卓操作に対応させます。
-
7エラーハンドリングを追加する
try-exceptでゼロ除算・不正入力を捕捉してエラーメッセージを表示します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。少しずつ機能を追加することで、Pythonのスキルが飛躍的に向上します。
💡 科学計算機能を追加する
mathモジュールを使ってsin・cos・sqrt・log などの科学計算ボタンを追加できます。
import math
if char == "sin":
result = math.sin(math.radians(float(self.expression)))
💡 計算履歴を記録する
計算結果をリストに保存し、ScrolledTextウィジェットで履歴一覧を表示する機能を追加しましょう。
💡 テーマを切り替えられるようにする
LIGHT_THEMEとDARK_THEMEの辞書を定義し、トグルボタンでテーマを切り替えられるようにしましょう。
8. よくある問題と解決法
❌ 計算結果が「3.0」のように表示される
原因:Python除算は常にfloatを返します。
解決法:is_integer()で整数かチェックしてint()に変換します。
if isinstance(result, float) and result.is_integer():
result = int(result)
❌ キーボード入力が効かない
原因:tkinterのbind()では、"+"や"*"などの記号をそのまま使うことができず、keysym名を指定する必要があります。
解決法:記号の代わりにkeysym名("plus"・"minus"・"asterisk"・"slash")を使い、数字は"Key-0"〜"Key-9"形式で指定してください。
# NG: 記号をそのまま使う
self.root.bind("<+>", ...) # 動作しない
# OK: keysym名を使う
self.root.bind("<plus>", ...) # +
self.root.bind("<asterisk>", ...) # *
self.root.bind("<Key-1>", ...) # 数字1
9. 練習問題
アプリの理解を深めるための練習問題です。難易度順に挑戦してみてください。
-
課題1:バックスペース機能
表示中の数式の末尾1文字を削除するバックスペースボタンを実装してください。
-
課題2:メモリ機能
M+(メモリに加算)・M-(メモリから減算)・MR(メモリ呼び出し)・MC(メモリクリア)の4つのメモリ機能ボタンを追加してください。
-
課題3:連続計算
「3 + 4 = 7」の後にさらに「+ 2 =」と打つと「9」になる連続計算機能を実装してください。