こんにちは、こちょすです。
今日はPythonで電卓を作ってみようと思います!💪
参考にするのはMacに標準搭載されている電卓です。皆さんも一度は使ったことはあるのではないでしょうか???
プログラミングを勉強し始めると、一番始めに学ぶような文字列操作や配列操作を学びつつ、役立つものを作れるので楽しいですよ!
それにエンジニアを目指す方なら自分でコードがかけるのは強いです!
コーディングスキル意外にもエンジニアに必要なスキルが知りたい方は以下の記事もみてくださいね🙏
-
-
数学苦手なド文系の僕がシステムエンジニアになれた5つの理由
続きを見る
では一緒にがんばっていきましょう🔥
こんな方におすすめ
- Pythonで何か物づくりがしてみたい
- Pythonを勉強し始めたけど次のSTEPに行きたい
- 簡単な処理で電卓を作ってみたい
- tkinterの使い方が知りたい
目次
Mac電卓の機能を整理していこう!
pythonのtkinterの使い方を学ぶ前に、まずはゴールを明確にしましょう!何を実装すればOKなんだっけ??
というのを明確にする必要があります。ちなみにこの作業をSE的にいうと要件定義といいます。
ぽちぽち電卓をいじりながら、要件を洗い出してみましょう!
チェックリスト
- 数字を打つと、打った順番に左から数列が表示される
- 演算子(+ー×÷)を初めて押しても何も起きない
- 演算子を押した後に、再度数字を押すと、その前に押していた数字がクリアされ、新たに数列が表示される
- 2回目に演算子を押すと、最初の数値と最初に押した演算子と2回目の数値にて計算した結果が表示される
- イコール(=)を押すと、直前の計算結果と直前の演算子と最後に入力した数値から計算した結果が表示される
- クリア(C)を押すと、0が表示され、それまでの計算結果がクリア(削除)される
- "+/-"を押すと、プラスとマイナスが入れ替わり、計算時にもその符号が反映される
- 割り算で余りなく割り切れた場合には".0"は表示しない
こんなところかなと思います!
全部は洗い出せないよーと心配になる方もいるかもしれません。仕事だと確かに漏れなく実装することが求められることもあるかもしれません(ウォーターフォールなら尚更)
しかし今回は遊びでやってみているだけなので、あまり気にせず進みましょう!
あとで気づけば直せばいいだけです!💪
tkinterを使って実装してみよう!
上で整理した要件に合わせて早速実装してみましょう!
今回はまずは全コードを出して、次の章で要点を説明していこうと思います。
もしできる方はまずは見ないで書いてみましょう!
# ライブラリインポート import tkinter as tk from tkinter import ttk # 計算用配列の宣言 entry_list = [] entry_list_str = [] entry_list_join = [] calc_list_a = [0] calc_list_b = [0] operand_list = [''] list_calc = [] list_calc_join = [] list_calc_result = [] # 数値入力処理 def add_number(num): display_list.set('') entry_list.append(num) entry_list_str = map(str, entry_list) entry_list_join.append(''.join(entry_list_str)) display_list.set(entry_list_join[-1]) # 計算処理 def calc_function(): if calc_list_b == '': pass else: if operand_list[-1] == '': pass else: calc_result.set(eval(str(calc_list_b[-1]) + str(operand_list[-1]) + str(calc_list_a[-1]))) if calc_result.get()[-2:] == '.0': calc_result.set(calc_result.get()[:-2]) display_list.set(calc_result.get()) calc_list_a.append(display_list.get()) # 加算処理 def plus_function(): calc_list_b.append(calc_list_a[-1]) calc_list_a.append(entry_list_join[-1]) calc_function() operand_list.append('+') entry_list.clear() # 減算処理 def minus_function(): calc_list_b.append(calc_list_a[-1]) calc_list_a.append(entry_list_join[-1]) calc_function() operand_list.append('-') entry_list.clear() # 乗算処理 def multiply_function(): calc_list_b.append(calc_list_a[-1]) calc_list_a.append(entry_list_join[-1]) calc_function() operand_list.append('*') entry_list.clear() # 除算処理 def devide_function(): calc_list_b.append(calc_list_a[-1]) calc_list_a.append(entry_list_join[-1]) calc_function() operand_list.append('/') entry_list.clear() # イコール押下時処理 def equal_function(): calc_result.set( eval(str(calc_list_a[-1]) + str(operand_list[-1]) + str(entry_list_join[-1]))) clear_function() display_list.set(calc_result.get()) # クリア処理 def clear_function(): entry_list.clear() calc_list_a.clear() calc_list_b.clear() operand_list.clear() calc_list_a.append('0') calc_list_b.append('0') operand_list.append('') display_list.set('0') # 符号変換処理 def sign_change_function(): display_list.set(eval(str(display_list.get()) + ' * -1')) calc_list_a.append(display_list.get()) # Tkインスタンスの生成 root = tk.Tk() # windowのタイトル設定 root.title("Calculator") # Tkインスタンスを引数としてフレームインスタンスの生成 mainframe = ttk.Frame(root, padding="3 3 12 12") # フレームのグリッドを指定 mainframe.grid(column=0, row=0, sticky=("N", "W", "E", "S")) root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) display_list = tk.StringVar() calc_result = tk.StringVar() # ラベルとボタンを生成 ttk.Label(mainframe, textvariable=display_list).grid( column=2, row=1, sticky=("W", "E")) ttk.Button(mainframe, text="0", command=lambda: add_number(0)).grid( column=1, row=6, sticky="W") ttk.Button(mainframe, text="1", command=lambda: add_number(1)).grid( column=1, row=5, sticky="W") ttk.Button(mainframe, text="2", command=lambda: add_number(2)).grid( column=2, row=5, sticky="W") ttk.Button(mainframe, text="3", command=lambda: add_number(3)).grid( column=3, row=5, sticky="W") ttk.Button(mainframe, text="4", command=lambda: add_number(4)).grid( column=1, row=4, sticky="W") ttk.Button(mainframe, text="5", command=lambda: add_number(5)).grid( column=2, row=4, sticky="W") ttk.Button(mainframe, text="6", command=lambda: add_number(6)).grid( column=3, row=4, sticky="W") ttk.Button(mainframe, text="7", command=lambda: add_number(7)).grid( column=1, row=3, sticky="W") ttk.Button(mainframe, text="8", command=lambda: add_number(8)).grid( column=2, row=3, sticky="W") ttk.Button(mainframe, text="9", command=lambda: add_number(9)).grid( column=3, row=3, sticky="W") ttk.Button(mainframe, text="+", command=lambda: plus_function()).grid( column=4, row=5, sticky="W") ttk.Button(mainframe, text="-", command=lambda: minus_function()).grid( column=4, row=4, sticky="W") ttk.Button(mainframe, text="×", command=lambda: multiply_function()).grid( column=4, row=3, sticky="W") ttk.Button(mainframe, text="÷", command=lambda: devide_function()).grid( column=4, row=2, sticky="W") ttk.Button(mainframe, text="=", command=lambda: equal_function()).grid( column=4, row=6, sticky="W") ttk.Button(mainframe, text="C", command=lambda: clear_function()).grid( column=1, row=2, sticky="W") ttk.Button(mainframe, text="+/-", command=lambda: sign_change_function()).grid( column=2, row=2, sticky="W") for child in mainframe.winfo_children(): child.grid_configure(padx=0, pady=0) root.mainloop()
tkinterの使い方解説 処理をブロックごとにみていこう!
上のコードをみてしまうと「うっ、、、」となってしまう方もいると思います。
まずはどういう構造になっているか、ざっくり書いておくと、以下のような流れで書かれています。
というわけで、ブロック単位でどんな処理をしているのかみていきましょう!🔥
tkinterの使い方解説 その1:①ライブラリのインポート〜②変数の宣言
# ライブラリのインポート import tkinter as tk from tkinter import ttk # 計算用配列の宣言 entry_list = [] entry_list_str = [] entry_list_join = [] calc_list_a = [0] calc_list_b = [0] operand_list = [''] list_calc = [] list_calc_join = [] list_calc_result = []
インポートしているのはtkinterと、tkinterモジュールの中のtkinterライブラリです。
紛らわしいのですが、ttkとしてインポートしているtkinter.tkinterを使うことで、ボタンやラベルなどがOSに合わせていい感じに表示されます。
ここはそんなに難しくありませんが、1つポイントがあります。
計算結果を入れる箱は変数ではなく、配列にしている pythonでは、変数は一度値を代入してしまうと、その値は変更できません。 この特徴を、「immutable(イミュータブル)である」といいます。 電卓では、計算を何度も行うので、計算結果を繰り返し入れられる箱があると便利ですが、普通に変数を宣言してしまうと、エラーになってしまします。 これを解決する方法はいくつかあります。 1つはクラスを使うことです。クラスを使ってインスタンス化したオブジェクトを使えば、再利用可能となります。 もう1つは配列を使うことです。これは直接的な解消方法ではありませんが、計算結果を次から次へと配列に追加してしまえば、上書き更新とか気にしなくていいですね。 今回は配列を使っています。その理由は機能拡張を意識しました。今回はMacの電卓を作ってみていますが、windowsの電卓を作りたいと思う人もいると思います。 windowsの電卓の場合、バージョンにもよりますが、計算の履歴を表示することができたりします。 配列を使っていると、過去の情報を持てるので、こういった機能を実装しやすくなると考えました。 今回はそこまでやりませんが、ぜひ挑戦してみてくださいね💪
tkinterの使い方解説 その2:③関数の宣言
# 数値入力処理 def add_number(num): display_list.set('') entry_list.append(num) entry_list_str = map(str, entry_list) entry_list_join.append(''.join(entry_list_str)) display_list.set(entry_list_join[-1])
これは数字ボタンが押された時の挙動を定義しています。
display_list
電卓の表示箇所に出す数字を出すためのtkinterオブジェクトです。
この解説はあとで出てきますが、今は表示するための箱なんだなあと思っておいてください!
数字が入力されたとき、その数字を並べて表示する必要があります。それを実現しているのが、entry_list.append(num)ですね。
ここで配列に入れ、それをentry_list_join.append(''.join(entry_list_str))で結合しています。
それぞれの変数に、どんな値が入っているのかはprintなどを使って適宜確認しながら進めてみることをお勧めします!
# 計算処理 def calc_function(): if calc_list_b == '': pass else: if operand_list[-1] == '': pass else: calc_result.set(eval(str(calc_list_b[-1]) + str(operand_list[-1]) + str(calc_list_a[-1]))) if calc_result.get()[-2:] == '.0': calc_result.set(calc_result.get()[:-2]) display_list.set(calc_result.get()) calc_list_a.append(display_list.get())
計算処理のコアの部分です。といってもそんなに複雑なことはしていないですのでご安心を!
ポイントはeval関数です、この関数を使って、数字2つと、演算子(operand)の計算結果を出しています。
eval関数
pythonに標準搭載されている組み込み関数です。
eval関数に文字列で計算式を渡すことで、それを数式として解釈して計算してくれる便利屋です!
例えば、eval('1' + '3')とすれば4が返ってきます。
eval関数は文字列を引数としてとるので、str関数を使って、文字列変換をしています。
また、割り算をした際に、計算結果が整数になった場合、eval関数は小数第一位まで返します。(ex:4/2 = 2.0)
しかしMacの電卓で実施してみるとわかりますが、この場合には「2」が表示され、小数点は表示されません。
これを実現するためにif calc_result.get()[-2:] == '.0':配下を実装しています。
pythonでは文字列を配列のように扱うことが可能なので、右から2byte取得し、整数かどうかを判断しています。
今回は文字列操作や配列操作を織り混ぜたかったので上記のようにしていますが、float.is_integer()関数などを使ってみてもいいと思います✍️
# 加算処理 def plus_function(): calc_list_b.append(calc_list_a[-1]) calc_list_a.append(entry_list_join[-1]) calc_function() operand_list.append('+') entry_list.clear()
四則演算は考え方は同じなので、加算処理を使って説明します。
計算時には3つの要素が必要となります。
2の2回目の入力結果は画面に表示されているので、それをcalc_list_aに入れます。(calc_list_a.append(entry_list_join[-1]))
1の1回目の入力結果は、前回四則演算ボタンを押したタイミングでcalc_list_aに入っているので、これをcalc_list_bに移します。
ここでポイント!
注意ポイント
先にcalc_list_a→calc_list_bの代入処理を行わないと、前回の入力内容が消えてしまうので、上のコードの順番で実施しましょう!
tkinterの使い方解説 その3:④tkinterオブジェクトの生成
# Tkインスタンスの生成 root = tk.Tk() # windowのタイトル設定 root.title("Calculator") # Tkインスタンスを引数としてフレームインスタンスの生成 mainframe = ttk.Frame(root, padding="3 3 12 12") # フレームのグリッドを指定 mainframe.grid(column=0, row=0, sticky=("N", "W", "E", "S")) root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) # tkinterクラスの宣言(文字列型) display_list = tk.StringVar() calc_result = tk.StringVar() # ラベルとボタンを生成 ttk.Label(mainframe, textvariable=display_list).grid( column=2, row=1, sticky=("W", "E")) ttk.Button(mainframe, text="0", command=lambda: add_number(0)).grid( column=1, row=6, sticky="W") ttk.Button(mainframe, text="1", command=lambda: add_number(1)).grid( column=1, row=5, sticky="W") ttk.Button(mainframe, text="2", command=lambda: add_number(2)).grid( column=2, row=5, sticky="W") ttk.Button(mainframe, text="3", command=lambda: add_number(3)).grid( column=3, row=5, sticky="W") ttk.Button(mainframe, text="4", command=lambda: add_number(4)).grid( column=1, row=4, sticky="W") ttk.Button(mainframe, text="5", command=lambda: add_number(5)).grid( column=2, row=4, sticky="W") ttk.Button(mainframe, text="6", command=lambda: add_number(6)).grid( column=3, row=4, sticky="W") ttk.Button(mainframe, text="7", command=lambda: add_number(7)).grid( column=1, row=3, sticky="W") ttk.Button(mainframe, text="8", command=lambda: add_number(8)).grid( column=2, row=3, sticky="W") ttk.Button(mainframe, text="9", command=lambda: add_number(9)).grid( column=3, row=3, sticky="W") ttk.Button(mainframe, text="+", command=lambda: plus_function()).grid( column=4, row=5, sticky="W") ttk.Button(mainframe, text="-", command=lambda: minus_function()).grid( column=4, row=4, sticky="W") ttk.Button(mainframe, text="×", command=lambda: multiply_function()).grid( column=4, row=3, sticky="W") ttk.Button(mainframe, text="÷", command=lambda: devide_function()).grid( column=4, row=2, sticky="W") ttk.Button(mainframe, text="=", command=lambda: equal_function()).grid( column=4, row=6, sticky="W") ttk.Button(mainframe, text="C", command=lambda: clear_function()).grid( column=1, row=2, sticky="W") ttk.Button(mainframe, text="+/-", command=lambda: sign_change_function()).grid( column=2, row=2, sticky="W")
tkinterの部品を配置する部分です。パーツの配置の仕方は、gridを使っています。
これはrow(行)とcolumn(列)を使って、何行何列目に配置するかを指定する方法です。
他にもpackなどの配置方法があるので、気になる方は是非調べてみてくださいね🔥
ここでの1番のひっかかりポイントはボタンの部分だと思います。
tkinterのボタン
インポートのところで書きましたが、ttkオブジェクトを使うことで、ボタンやラベルをいい感じの表示にしてくれます。デメリットとしては色や形を変えたりできません。(あくまでtkinter側でいい感じにしてくれる)
もしもう少し電卓っぽい色にしたい場合、tkオブジェクトのbuttonを使うことで自分の好きなように変えることができますので、是非チャレンジしてみてください💪
ボタンごとに、command引数を取っています。ここで上で定義したどの関数を呼び出すのかを師弟しています。
command=sign_change_function()のように、引数を指定しない呼び出し方をすると、tkinter起動時に即時に関数を呼び出してしまいます。これをしないようにするため、無名関数であるlambdaを使っています。
command=lambda: sign_change_function()
tkinterの使い方解説 その4:⑤tkinterオブジェクトの配置
for child in mainframe.winfo_children(): child.grid_configure(padx=0, pady=0) root.mainloop()
最後に上で定義したパーツを、forループで取得し、grid_configureで配置したら完成です!
いかがでしたでしょうか?
最初は「うっ」となるようなコードでも、分解してみるとそんなに難しいことはしていないと思ってもらえたら嬉しいです。
何万、何百万STEPの大きなコードでも、こうして小さなコード群が組み合わさって作られているので、分解しながら読み進められるようになれば怖いもの無しです!💪