科学電卓
四則演算にsin・cos・tan・log・√を加えた科学電卓。ボタンレイアウトとmathモジュールを学べる。
1. アプリ概要
四則演算にsin・cos・tan・log・√を加えた科学電卓。ボタンレイアウトとmathモジュールを学べる。
このアプリはconvertersカテゴリの実践的なPythonアプリです。使用ライブラリは tkinter(標準ライブラリ)、難易度は ★★☆ です。
このアプリは「変換ツール」カテゴリです。形式変換ツールは入力・処理・出力の三層を明示的に書く好材料で、エラー入力の扱いやバリデーションの基本を体験できます。tkinter(標準ライブラリ) を活かして実装するこの構造は、他のアプリにも応用が効きます。
動かしながら読むことが理解の最短経路です。まずはコードをコピーして実行し、想定どおりに動くことを確認したうえで解説と照らし合わせてください。
カスタマイズでは「機能追加」「UI 改善」「エラー耐性」の三方向で考えると視野が広がります。練習問題にもそれぞれの方向の具体例を用意しています。
2. 機能一覧
- ループで生成するボタン群を
on_button()に接続 messagebox.showerror()によるダイアログ通知- ウィンドウはタイトル「科学電卓」・サイズ 440x520 で起動
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
import math
class App076:
"""科学電卓"""
def __init__(self, root):
self.root = root
self.root.title("科学電卓")
self.root.geometry("440x520")
self.root.configure(bg="#f8f9fc")
self.expr = ""
self.angle_mode = "DEG" # DEG / RAD
self._build_ui()
def _build_ui(self):
"""UIを構築する"""
title_frame = tk.Frame(self.root, bg="#3776ab", pady=10)
title_frame.pack(fill=tk.X)
tk.Label(
title_frame, text="科学電卓",
font=("Noto Sans JP", 14, "bold"),
bg="#3776ab", fg="white"
).pack()
# ディスプレイ
self.display_var = tk.StringVar(value="0")
disp = tk.Entry(
self.root, textvariable=self.display_var, font=("Arial", 18),
justify="right", bd=4, relief=tk.SUNKEN, state="readonly", readonlybackground="white"
)
disp.pack(fill=tk.X, padx=10, pady=8)
# 角度モード切替
mode_frame = tk.Frame(self.root, bg="#f8f9fc")
mode_frame.pack(anchor="w", padx=12)
self.mode_var = tk.StringVar(value="DEG")
ttk.Radiobutton(mode_frame, text="DEG", variable=self.mode_var, value="DEG", command=self._set_mode).pack(side=tk.LEFT)
ttk.Radiobutton(mode_frame, text="RAD", variable=self.mode_var, value="RAD", command=self._set_mode).pack(side=tk.LEFT)
# ボタンレイアウト
btn_frame = tk.Frame(self.root, bg="#f8f9fc")
btn_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=8)
layout = [
["sin", "cos", "tan", "log", "ln"],
["√", "x²", "(", ")", "π"],
["7", "8", "9", "/", "C"],
["4", "5", "6", "*", "←"],
["1", "2", "3", "-", "^"],
["0", ".", "=", "+", "e"],
]
for r, row in enumerate(layout):
for c, label in enumerate(row):
btn = ttk.Button(btn_frame, text=label, command=lambda l=label: self.on_button(l))
btn.grid(row=r, column=c, sticky="nsew", padx=2, pady=2)
btn_frame.rowconfigure(r, weight=1)
for c in range(5):
btn_frame.columnconfigure(c, weight=1)
def _set_mode(self):
"""角度モードを更新"""
self.angle_mode = self.mode_var.get()
def on_button(self, label):
"""ボタンに応じた処理"""
if label == "C":
self.expr = ""
elif label == "←":
self.expr = self.expr[:-1]
elif label == "=":
self._calculate()
return
elif label == "x²":
self.expr += "**2"
elif label == "√":
self.expr += "sqrt("
elif label == "^":
self.expr += "**"
elif label == "π":
self.expr += "pi"
elif label == "e":
self.expr += "e"
elif label == "ln":
self.expr += "ln("
elif label in ("sin", "cos", "tan", "log"):
self.expr += f"{label}("
else:
self.expr += label
self.display_var.set(self.expr or "0")
def _calculate(self):
"""式を評価して結果を表示"""
expr = self.expr
if not expr:
return
try:
result = self._safe_eval(expr)
if isinstance(result, float) and result.is_integer():
result = int(result)
self.display_var.set(str(result))
self.expr = str(result)
except Exception as e:
messagebox.showerror("エラー", f"計算できません: {e}")
self.expr = ""
self.display_var.set("0")
def _safe_eval(self, expr):
"""限定スコープで式を評価する"""
# 角度モードに応じた三角関数
if self.angle_mode == "DEG":
f_sin = lambda x: math.sin(math.radians(x))
f_cos = lambda x: math.cos(math.radians(x))
f_tan = lambda x: math.tan(math.radians(x))
else:
f_sin, f_cos, f_tan = math.sin, math.cos, math.tan
names = {
"__builtins__": {},
"sin": f_sin, "cos": f_cos, "tan": f_tan,
"log": math.log10, "ln": math.log,
"sqrt": math.sqrt,
"pi": math.pi, "e": math.e,
"abs": abs, "pow": pow,
}
return eval(expr, names, {})
if __name__ == "__main__":
root = tk.Tk()
app = App076(root)
root.mainloop()
5. コード解説
科学電卓のコードを、実際に書かれている実装に沿って解説します。
クラス設計とコンストラクタ
App076 クラスにアプリの全機能をまとめています(メソッド6個・全134行)。__init__ ではタイトル「科学電卓」とウィンドウサイズ 440x520 を設定します。あわせて状態を保持する変数(self.expr・self.angle_mode)を初期化し、最後に _build_ui() で画面を組み立てます。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
ウィジェット構成
画面は tk.Frame×3・tk.Label・tk.Entry・ttk.Radiobutton×2・ttk.Button(ループで生成) で構成しています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
イベント処理とボタンの接続
ボタンはループ内で command=lambda l=label: self.on_button(l) の形で生成しています。既定引数でループ変数を固定するのがポイントで、これを書かないと全ボタンが最後の値で動いてしまいます。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
例外処理
try-except で Exception を捕捉しています。あわせて messagebox.showerror() のダイアログで、ユーザーへの通知・確認を行います。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1ファイルを作成する
新しいファイルを作成して app076.py と保存します。使うのは
math・tkinterだけなので、追加インストールは不要です。 -
2クラスの骨格を作る
App076クラスを定義し、__init__と、末尾のroot = tk.Tk()~mainloop()の最小構成を書いて、まず空のウィンドウが出ることを確認します。 -
3ウィンドウを設定する
title("科学電卓")・geometry("440x520")・configure(bg="#f8f9fc")をコンストラクタで設定します。 -
4画面部品を並べる
_build_ui()の中で、tk.Frame×3・tk.Label・tk.Entry・ttk.Radiobutton×2・ttk.Button(ループで生成) を作って配置します(掲載コードと同じ並び順で書くとレイアウトが一致します)。まず表示だけ確認しましょう。 -
5イベントを接続する
ループ生成のボタンは
command=lambda l=label: self.on_button(l)の形で接続します。 -
6中心になるメソッドを実装する
アプリの本体である
on_button()(26行)・_safe_eval()(19行)・_calculate()(15行) を実装します。 -
7動作確認する
python app076.pyで起動し、各ボタンが反応することを確認します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。
💡 ダークモードを追加する
bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。
💡 結果をファイルに保存する
このコードに保存処理はないため、表示中の数値や結果はアプリを閉じると消えます。テキストファイルへ書き出して、次回起動時に読み込む機能を追加してみましょう。
💡 設定ダイアログ
フォントサイズや色などの設定をユーザーが変更できるオプションダイアログを追加しましょう。
8. よくある問題と解決法
❌ 文字のフォントが崩れる・見た目が違う
原因:このコードは font=("Noto Sans JP", ...) のようにフォント名を直接指定しています。環境にこのフォントが入っていないと、OS が代わりのフォントで表示するため、見本と見た目が変わることがあります。
解決法:動作には支障ありません。気になる場合は font 引数の名前を自分の OS に入っているフォントへ書き換えるか、font 引数を省略してください。
❌ 写経したら全部のボタンが同じ動きになる
原因:ループでボタンを作るとき、このコードは command=lambda l=label: self.on_button(l) のように既定引数でループ変数を固定しています。これを省略してループ変数を直接書くと、ループ終了後の最後の値が全ボタンで共有されてしまいます(Python のクロージャの有名な罠です)。
解決法:掲載コードのとおり、lambda の既定引数でその時点の値を渡してください。
❌ ウィンドウの大きさが画面に合わない
原因:geometry("440x520") の固定サイズで起動するためです。
解決法:この数値を書き換えて起動サイズを調整してください。なお、このコードにはウィンドウサイズを固定する設定(resizable の指定)が無いため、ウィンドウの端をドラッグしたサイズ変更は既定どおり可能です。
9. 練習問題
アプリの理解を深めるための練習問題です。気になるものから挑戦してみてください(課題3は定型の発展課題です)。このアプリは学習用のサンプルです。_safe_eval() は組み込み関数を空にしたうえで eval() を呼びますが、外部から受け取った文字列をそのまま渡す使い方は避けてください。
-
課題1:= のあとの数字入力を新しい計算として始める
1+2=と押して 3 が出たあと、続けて9を押すと表示は39になります。計算が終わったあとの数字入力は、新しい式の始まりとして扱うようにしましょう。期待結果:
1+2=のあとに9を押すと、いまは39。直したあとは9だけが表示される。結果に演算子を続ける使い方は残す。合格条件
1+2=のあとに9を押すと表示が9になる1+2=のあとに+5=と押すと 8 が出るCを押したあとの入力は今までどおり
ヒント①(どこを触るか): 触るのは3つのメソッドです。
__init__()にself.just_calculated = Falseを1行足し、_calculate()で計算に成功したときにTrueにして、on_button()の先頭で判定します。_safe_eval()は触りません。
ヒント②(使うもの):on_button()の先頭でif self.just_calculated and (label.isdigit() or label == "."):と判定し、self.expr = ""に戻してからいつもの処理へ進みます。判定したらフラグはFalseに戻し、_calculate()が結果を入れた直後だけTrueにします。
つまずきやすい点: 数字以外まで巻き込むと、+や=でも式が消えて、結果に続けた計算ができなくなります。label.isdigit()は7のような数字ボタンにだけ当てはまり、sinやx²では False です。フラグを戻し忘れると、2回目以降の入力が毎回リセットされます。 -
課題2:閉じかっこの押し忘れを = のときに補う
√ボタンはsqrt(を入れるだけなので、閉じかっこは自分で押す必要があります。押し忘れたまま=を押すとエラーになるため、足りない分を自動で補いましょう。期待結果:
√9=の順に押すと、いまはエラーのダイアログが出て表示が 0 に戻る。補うようにすると 3 が表示される。合格条件
√9=で 3 が表示される(1+2)*3=は今までどおり 9 になる- 閉じかっこの方が多い式(
1+2))は、いままでどおりエラーになる
ヒント①(どこを触るか): 触るのは
_calculate()の1か所です。expr = self.exprの直後、self._safe_eval(expr)に渡す前に補います。on_button()の√の分岐(self.expr += "sqrt(")は触りません。
ヒント②(使うもの):missing = expr.count("(") - expr.count(")")で不足数を数え、if missing > 0: expr += ")" * missingと足します。書き換えるのはローカル変数のexprです。self.exprのほうは、計算に成功すると結果の文字列で上書きされます。
つまずきやすい点: 数えずに常に")"を1つ足すと、正しく閉じた式まで壊れてエラーになります。missingが負のとき(閉じかっこが多いとき)は補えないので、エラー表示に任せます。sinやlogも同じ(を使うため、この1か所でまとめて効きます。 -
課題3:保存機能の追加
処理結果をファイルに保存する機能を追加しましょう。
写経中に赤いエラー文が出たら、Pythonエラー一覧&解決法(英語メッセージ逆引き)で原因と直し方をすぐ確認できます。
写経しながら「なぜこう書くのか」が気になり始めたら、入門書を1冊通して読むと断片的な知識がつながります。Python入門書のおすすめ2冊(当サイトの参考書ランキング総合1〜2位)で、独学者向けの最初の1冊を比較しています。