Tkinterでデスクトップアプリを作成しPyinstallerで実行ファイルにして配布する
はじめに
この記事ではPythonのTkinterで簡易的なデスクトップアプリを作成し、Pyinstallerで実行ファイルに変換して配布するまでの手順を解説します。
なお、Pyinstallerはクロスプラットフォームには対応していないため、アプリを起動したいOS上で実行ファイルに変換する必要があります。
開発環境
OS: Windows10
Python: 3.8.12
miniconda3: 4.14.0
Tkinter: 8.6
Pyinstaller: 5.6.2
作業手順
1. 事前準備
miniconda3やPyinstaller等の必要なツールをインストールします。ここでは詳細は割愛します。
2. minicondaの仮想環境を作成する
まず、アプリの実行環境となるminicondaの仮想環境を作成します。
conda create --name appenv python=3.8.12
仮想環境が作成されているか確認します。
conda env list
仮想環境に入ります。
conda activate appenv
必要なパッケージをインストールしておきます。ここでインストールしたパッケージは後程pyinstallerで実行ファイルを作成した際にアプリに同梱されます。
conda install pyinstaller
3. Tkinterでデスクトップアプリを作成
Tkinterでデスクトップアプリを作成します。
import tkinter as tk
def main():
# メインウィンドウを作成
root = tk.Tk()
root.geometry("300x130")
root.title("Add Number App")
# ラベルを作成して初期値を設定
label = tk.Label(root, text="0", font=("Helvetica", 24))
label.pack(padx=20, pady=20)
# ボタンを作成
button = tk.Button(root, text="+1を追加", command=prep_func(label))
button.pack()
# アプリを実行
root.mainloop()
def prep_func(label):
def increment_number():
current_number = int(label["text"])
new_number = current_number + 1
label["text"] = str(new_number)
return increment_number
if __name__ == "__main__":
main()
おおよそシンプルな処理ですが、tk.Button
の引数command
への関数の渡し方について、少し補足します。
前提として、command
には関数オブジェクトを渡す必要があります。しかし、関数オブジェクトでは引数は渡せないため、少し工夫が必要になります。
そこで、関数オブジェクト(increment_number
)を返す関数(prep_func()
)を定義し、関数オブジェクト内に処理を書いています。
def prep_func(label):
# 関数内に関数を定義
def increment_number():
current_number = int(label["text"])
new_number = current_number + 1
label["text"] = str(new_number)
# 関数オブジェクトを返す
return increment_number
ここでincrement_number()
に処理を書かずにprep_func()
にそのまま処理を書いてしまうと、アプリ起動時に(ボタンを押さずとも)関数が実行されてしまい意図した挙動になりません。
そのような事態を回避するため、上記のような少しわかりづらい書き方になっています。
上記のアプリを以下のコマンドで起動してみます。
python app.py
以下のようなアプリが立ち上がります。
ボタンを押してみると、数字が増えていくことが確認できます。
4. Pyinstallerで実行ファイルに変換
次に、ステップ2で作成したアプリケーションをPyinstallerを用いて実行ファイルに変換します。
方法は簡単で、以下のコマンドを実行するだけです。
pyinstaller app.py --onefile --noconsole
オプションについて補足します。
option | 説明 |
---|---|
--onefile |
アプリケーションを1つの実行ファイルにまとめます。指定しない場合、複数ファイルに分割されます。 |
--noconsole |
アプリケーション起動時に、コンソールが起動しなくなります。開発初期はエラー確認などのためにコンソールが起動した方が便利ですが、アプリが完成したりログ出力の環境が整ったら不要になるので、このオプションを設定することでスタイリッシュにアプリを起動できます。 |
上記コマンドの処理が成功すると、distフォルダの中にapp.exe
が生成されます。
app.exe
を実行してアプリが起動すれば成功です。
なお、コマンド実行後にはspecファイルも作成され、2回目以降はspecファイルを指定することで同じ設定内容でコマンドを実行することができます。
pyinstaller app.spec
このspecファイルを書き換えることで直接設定を変更することも可能です。詳細については公式ドキュメントをご参照ください。
補足1:アプリの中にファイルを含めたい場合
アプリの中にフォルダやファイルを含めたい時があると思います。例えば、テンプレートファイルに沿って新しいファイルを生成するアプリケーションを作る時のテンプレートファイルなどです。
そのようなときに、Pyinstallerにはアプリ内にファイルを含める--add-data
というオプションが用意されています。
書き方は以下の通りです。
pyinstaller app.py --onefile --add-data "Template.docx;."
{同梱したいファイル};{同梱後のファイル名}
という書き方で指定します。
同梱したファイルは、実行時に別のフォルダに展開されるようになっており、その際に付けられる名前が「同梱後のファイル名」にあたります。上記の書き方だと元のファイル名と同じファイル名で展開されます。ここで指定した名前を用いてアプリ内でファイルを参照することができます。(参考)
なお、ファイルだけでなくフォルダも指定できますが、その際には同梱後のフォルダ名を"."ではなく明示的に指定しなければうまく動作しませんでした。(例:source_dir;source_dir
)
同梱するデータについては、コマンドで指定するだけでなくspecファイル内で直接指定することも可能です。
補足2:アプリ内で外部ツールのコマンドを実行したい場合
補足1の応用で、上記の--add-data
オプションを用いてコマンドの実行ファイルをアプリに含めることで、アプリ内でコマンドを実行することができます。
例えば、ドキュメントを暗号化するコマンドがあったとして、アプリ内でコマンドを実行したい場合、特に何も設定せずにアプリを実行ファイル化すると、アプリ内でコマンドを実行した際に「コマンドがない」とエラーが出てしまいます。
そこで、--add-data
にコマンドの実行ファイル(および必要に応じて関連ファイル)を含めて、pythonスクリプト内で(subprocess等を用いて)その実行ファイルのパスを指定することで、コマンドを実行することができます。
Pythonスクリプト内
def resource_path(relative_path):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
encript_path = resource_path("encript.exe")
subprocess.call(encript_path, shell=True)
実行ファイル化
pyinstaller app.py --onefile --add-data "Template.docx;." --add-data "encrypt.exe;."
参考文献
Discussion