毎年、仕事量が1.5倍ぐらい増えてる。
もう一つの課レベルの仕事は十分にやってるよ。
流石に辛くなってきた……。
という状態で、最近の週末は英気を養うのに専念してる。
週末に何か作りたくなるなんて、ワーカホリックだよ。ブログ更新だって辛い。
5chまとめサイトでも見ながら、ボーっとするに限る。
ふむ。
Pythonは初心者に進めないほうがよいのねーー。
なんでだっけ?
どうやら「GUIアプリが作れずコンソールだと楽しくない」というのが理由っぽい。
んな訳がないでしょ!
どんなゆとり世代だよ、調べろよ!
Python でGUIアプリを作成する
GUIとは、「Graphical User Interface」のことで視覚的に操作することが出来るUIのこと。
PythonでGUIを作成する際は、
TkInter、Qt、Kivy、WxPythonなど
を使う。
簡単な実装の「PySimpleGUI」も紹介する。
ライブラリ | ライセンス | 特徴 |
---|---|---|
TkInter | PSF(標準) | インストール不要、機能が少ない |
Qt | PyQt(GPL) pyside(LGPL) |
多機能でデザインがいい |
Kivy | MIT | GUIライブラリの中で近年もっとも人気が高い |
WxPython | wxWindows | C++で記述されているため高速 |
PySimpleGUI | LGPL3 | コードの書き方がシンプルで直感的でわかりやすい |
標準ライブラリ(TkInter)以外は、どのライブラリでも、クロスプラットフォームで高速。
なお、Pythonは2022年10月3日にリリースのPython 3.11を使って試している。
【Python GUI 1】 Tkinterについて
Tkinterは標準のPythonのGUIライブラリ。
個人的に考えるメリット、デメリットは以下の通り。
【メリット】
- 標準だからインストール不要
- サンプルはそこそこ多い
【デメリット】
- ウィジェット(UIパーツ)を配置する際、種類、オプション、レイアウトと順を追って設定しないといけない
- ウィジェットの配置が増えると、レイアウトがコードを見てもよくわからない
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# -*- coding: utf-8 -*- import tkinter from tkinter import messagebox #ボタンがクリックされたら実行 def button_click(): input_name_value = input_name.get() input_address_value = input_address.get() input_phone_value = input_phone.get() show_message = "名前:" + input_name_value + 'が入力されました。\n' show_message += "住所:" + input_address_value + 'が入力されました。\n' show_message += "電話番号:" + input_phone_value + "が入力されました。" print(show_message) # 入力内容をポップアップ画面で表示 messagebox.showinfo("入力内容" ,show_message) #ウインドウの作成 root = tkinter.Tk() root.title("Python GUI") root.geometry("360x120") # 名前 input_name_label = tkinter.Label(text="名前") input_name_label.grid(row=1, column=1, padx=10,) # 入力欄の作成 input_name = tkinter.Entry(width=40) input_name.grid(row=1, column=2) # 住所 input_address_label = tkinter.Label(text="住所") input_address_label.grid(row=2, column=1, padx=10,) # 住所入力欄の作成 input_address = tkinter.Entry(width=40) input_address.grid(row=2, column=2) # 電話番号 input_phone_label = tkinter.Label(text="名前") input_phone_label.grid(row=3, column=1, padx=10,) # 電話番号入力欄の作成 input_phone = tkinter.Entry(width=40) input_phone.grid(row=3, column=2) #ボタンの作成 button = tkinter.Button(text="実行ボタン",command=button_click) button.place(x=10, y=80) #ウインドウの描画 root.mainloop() |
ファイル名を「tkinter.py」などにすると「ImportError: cannot import name ‘messagebox’ from partially initialized module ‘tkinter’」というエラーに悩まされるので注意を。
1 |
$ python tkinter_ui.py |
【Python GUI 2】 Qtについて
仕事でもお世話になっているライブラリ。
PyQtとPySideはいずれも、C++向けGUIフレームワークのQtのPythonラッパー。
ライセンスを考えてPySideを使う。PySide は元々 QT4 向けのライブラリだが、QT5 に対応するために新たに PySide2 が開発された。
使うには、まずはインストール……でエラー。
1 2 3 |
# /c/Python311/Scripts/pip install PySide2 ERROR: Could not find a version that satisfies the requirement PySide2 (from versions: none) ERROR: No matching distribution found for PySide2 |
次のページを見ると今のところPython3.6からPython3.9まででしかサポートしてない。
対応が遅いなぁ。
とりあえず、ソースコードはこんな感じになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import sys from PySide2.QtWidgets import QDialog, QApplication from PySide2.QtWidgets import QHBoxLayout, QVBoxLayout from PySide2.QtWidgets import QLineEdit, QLabel, QPushButton class Form(QDialog): """""" def __init__(self, parent=None): """Constructor""" super(Form, self).__init__(parent) main_layout = QVBoxLayout() name_layout = QHBoxLayout() lbl = QLabel("Name:") self.name = QLineEdit("") name_layout.addWidget(lbl) name_layout.addWidget(self.name) name_layout.setSpacing(20) add_layout = QHBoxLayout() lbl = QLabel("Address:") self.address = QLineEdit("") add_layout.addWidget(lbl) add_layout.addWidget(self.address) phone_layout = QHBoxLayout() self.phone = QLineEdit("") phone_layout.addWidget(QLabel("Phone:")) phone_layout.addWidget(self.phone) phone_layout.setSpacing(18) button = QPushButton('Submit') main_layout.addLayout(name_layout, stretch=1) main_layout.addLayout(add_layout, stretch=1) main_layout.addLayout(phone_layout, stretch=1) main_layout.addWidget(button) self.setLayout(main_layout) if __name__ == "__main__": app = QApplication([]) form = Form() form.show() sys.exit(app.exec_()) |
次なようなGUIが表示されるらしいけど未確認。
【Python GUI 4】 kivy
これは使ったことが無い。
標準ライブラリではないので、各自インストールする必要がある。
インストールしてみるとエラー。
1 2 3 4 5 6 7 |
$ /c/Python311/Scripts/pip install kivy Collecting kivy Downloading Kivy-2.1.0.tar.gz (23.8 MB) .... ERROR: Could not find a version that satisfies the requirement kivy_deps.sdl2_dev~=0.4.5 (from versions: 0.5.1) ERROR: No matching distribution found for kivy_deps.sdl2_dev~=0.4.5 [end of output] |
どうやら、現在Python3.11で利用可能なkivy は配布されてないらしい。
残念だけど、ソースコードはこんな感じになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
from kivy.app import App from kivy.uix.label import Label from kivy.uix.textinput import TextInput from kivy.uix.gridlayout import GridLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.core.window import Window class KivyApp(App): def build(self): self.title = "Login Screen" Window.size = (400,200) layout = GridLayout(cols=2,rows=2,padding=10,spacing=10,row_default_height=30) usernameinput = TextInput() passwordinput = TextInput(password=True) usernamelbl = Label(text="Username",size_hint_x=None, width=100) passwordlbl = Label(text="Password",size_hint_x=None, width=100) layout.add_widget(usernamelbl) layout.add_widget(usernameinput) layout.add_widget(passwordlbl) layout.add_widget(passwordinput) main_layout = BoxLayout(orientation='vertical',padding=10,spacing=10) main_layout.add_widget(layout) loginbutton = Button(text="Login") main_layout.add_widget(loginbutton) return main_layout if __name__ == '__main__': app = KivyApp() app.run() |
仮に実行したら次のようなGUIが表示される。
デザインはモダンな印象を受ける。
【Python GUI 5】 WxPython
Python GUIで最初に出会ったライブラリ。
こちらも標準ライブラリではないので、各自インストールすること。
1 2 3 |
$ /c/Python311/Scripts/pip install wx ERROR: Could not find a version that satisfies the requirement wx (from versions: none) ERROR: No matching distribution found for wx |
これもエラー。全然、ライブラリの対応が追いついてないじゃん。
でソースコードはこんな感じになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
import wx; #importing wxpython packages class myframe(wx.Frame):#Inheriting the "wx.Frame" class into our myframe class #our constructor def __init__(self,parent,title): #calling the constructor of wx.Frame wx.Frame.__init__(self,parent,-1,title); #Our panel basepanel=wx.Panel(self,-1); #some custom fonts #syntax:wx.Font(size,font_family,font_style,font_weight) font1 = wx.Font(12, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.LIGHT); font = wx.Font(15, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.BOLD) #this is the BoxSizer #syntax:wx.BoxSizer(orientation) basebox=wx.BoxSizer(wx.VERTICAL); #This is our name field ip_box=wx.BoxSizer(wx.HORIZONTAL);#A boxresizer ip_label=wx.StaticText(basepanel,label="User name");#Label "Username" ip_label.SetFont(font1)#setting custom font to label ip_text=wx.TextCtrl(basepanel,-1,"enter text here");#TextField for Username #adding Username label & its textfield to ip_box BoxSizer #syntax:Add(parent,id,proportion,spacing) ip_box.Add(ip_label,-1,wx.ALL,5);#wx.ALL means spacing of 5px in "all direction" ip_box.Add(ip_text,-1,wx.ALL,5); #password field pwd_box=wx.BoxSizer(wx.HORIZONTAL); pwd_label=wx.StaticText(basepanel,label="Password"); pwd_label.SetFont(font1); pwd_text=wx.TextCtrl(basepanel,-1); pwd_box.Add(pwd_label,-1,wx.ALL,5); pwd_box.Add(pwd_text,-1,wx.ALL,5); #WELCOME title title_box=wx.BoxSizer(wx.HORIZONTAL); title_text=wx.StaticText(basepanel,label="Login Frame");#a non-editable text title_text.SetFont(font); title_box.Add(title_text,-1); #OK button bt_box=wx.BoxSizer(wx.HORIZONTAL); ok_bt=wx.Button(basepanel,-1,"Login"); cancel_bt=wx.Button(basepanel,-1,"Cancel"); bt_box.Add(ok_bt,-1,wx.ALL,5); bt_box.Add(cancel_bt,-1,wx.ALL,5); #adding everything to basebox basebox.Add(title_box,0,wx.CENTER,10); basebox.Add(wx.StaticLine(basepanel),0,wx.ALL|wx.EXPAND,5); basebox.Add(ip_box,0,wx.CENTER,10); basebox.Add(pwd_box,0,wx.CENTER,10); basebox.Add(bt_box,0,wx.CENTER,10); #adding basebox to basepanel basepanel.SetSizer(basebox); #automatically setting up the frame size basebox.Fit(self); myapp=wx.App(); tempframe=myframe(None,"MyFrame");#our frame instance tempframe.Show(True);#making frame visible myapp.MainLoop(); |
実行できたら次のようなGUIが表示されるんだってさ。
【Python GUI 6】 PySimpleGUI
軽量なラッパーとして利用されている。
これも標準ライブラリではないので、各自インストールが必要。
1 |
$ /c/Python311/Scripts/pip install PySimpleGUI |
ソースコードはこんな感じになる。
これは動作した。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import PySimpleGUI as sg from random import randint import numpy as np from scipy import signal MAX_ROWS = MAX_COL = 10 board = [[randint(0,1) for j in range(MAX_COL)] for i in range(MAX_ROWS)] board = np.zeros(MAX_ROWS*MAX_COL).astype(np.int) idx = np.random.choice(len(board), 3, replace=False) # 爆弾をランダムに3箇所設置 board[idx] = 1 board = board.reshape(MAX_ROWS, MAX_COL).tolist() filter = np.array([ # 二次元畳み込みで爆弾の数をカウント [1,1,1], [1,0,1], [1,1,1] ]) counter = signal.convolve2d(board, filter, 'full') # 畳み込みとサイズが大きくなるので、 # スライスする。ここでboardを引くことで、爆弾マスは-1になる board = counter[1:-1,1:-1] - board print (board) board = board.tolist() layout = [[sg.Button('?', size=(4, 2), key=(i,j), pad=(0,0)) for j in range(MAX_COL)] for i in range(MAX_ROWS)] window = sg.Window('Minesweeper', layout) while True: event, values = window.read() print (event,values) if event in (None, 'Exit'): break select = sg.popup('マスに対する操作を選択してください',custom_text=('open', 'flag'),title='') print('select', select) if select is 'open': window[event].update(board[event[0]][event[1]], button_color=('white','black')) if board[event[0]][event[1]]==-1: sg.popup("You Lose") break elif select is 'flag': state = window[event].GetText() if state == '?': window[event].update('F', button_color=('red',None)) elif state == 'F': window[event].update('?', button_color=('white',None)) window.close() |
ゲーム「マインスイーパー」の実装だけど、コードは短い。
GUIは次のように表示される。
Python スクリプトを exe ファイル (実行ファイル) 化
せっく作ったGUIだから皆に使ってもらいたいよね。
「PyInstaller」を使用して、Python スクリプトを exe ファイル (実行ファイル) 化し、Python の実行環境がない Windows 上でも実行できるようにしてみよう。
まずはpyinstallerをインストールしていく。
1 |
$ /c/Python38/Scripts/pip install pyinstaller |
次に、exeファイル化するスクリプトファイルを準備する。
1 |
$ /c/Python38/Scripts/pyinstaller.exe tkinter_ui.py |
コンソール画面を非表示にするには次のように行う。
1 |
$ pyinstaller test.py --noconsole |
更に、単独ファイル化は次のように行う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
$ /c/Python38/Scripts/pyinstaller.exe --onefile --noconsole tkinter_ui.py 74 INFO: PyInstaller: 4.2 74 INFO: Python: 3.8.5 74 INFO: Platform: Windows-10-10.0.18362-SP0 79 INFO: UPX is not available. 85 INFO: Extending PYTHONPATH with paths 108 INFO: checking Analysis 177 INFO: checking PYZ 195 INFO: checking PKG 197 INFO: Building because toc changed 197 INFO: Building PKG (CArchive) PKG-00.pkg 2658 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully. 2673 INFO: Bootloader c:\python38\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe 2673 INFO: checking EXE 2674 INFO: Rebuilding EXE-00.toc because tkinter_ui.exe missing 2674 INFO: Building EXE from EXE-00.toc 2677 INFO: Copying icons from ['c:\\python38\\lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico'] 2726 INFO: Writing RT_GROUP_ICON 0 resource with 104 bytes 2726 INFO: Writing RT_ICON 1 resource with 3752 bytes 2726 INFO: Writing RT_ICON 2 resource with 2216 bytes 2726 INFO: Writing RT_ICON 3 resource with 1384 bytes 2726 INFO: Writing RT_ICON 4 resource with 37019 bytes 2726 INFO: Writing RT_ICON 5 resource with 9640 bytes 2726 INFO: Writing RT_ICON 6 resource with 4264 bytes 2726 INFO: Writing RT_ICON 7 resource with 1128 bytes 2779 INFO: Updating resource type 24 name 1 language 0 2839 INFO: Building EXE from EXE-00.toc completed successfully. |
何やらよく分からないコードが次々と現れてるが、じっと待機する。
そして、対象のディレクトリ内に幾つかのファイルが自動生成される。
1分くらい待たされたが、無事処理は終了。
これでexeファイルが出来上がっているはずなので、distと書かれたフォルダを開く。
すると中にtkinter_ui.exeがある。
9MBのファイル
がね。
大きいな……
そして、
PySimpleGUI で作ったアプリは60MB
まじかよーーーーー
デカすぎ。
おわりに
ネット上からダウンロードできるフリーソフトのexeでも大きくて2~3MBくらいのはずだけど……、ライブラリ等を包含しているためにサイズが大きいのだろう。
そして起動はめっちゃ遅い。
あまりEXE化はオススメできない。
まぁ今回は「PythonでGUI開発できる」ことを伝えたかったので、その点では問題ないか。
あれ?
僕、休日はゆっくり休むんじゃなかったっけ?