Kivyでアプリ開発をしていると、MVCのように処理と表示を分けたくなることがあると思います。
その時、ボタンをトリガーにすると、ボタンのフィードバックのタイミングが処理の終了タイミングと同じになってしまうことはないでしょうか。
今回は、ボタンのフォードバックと、重い処理とを分けて表示する方法を紹介します。
結論:Threadを使って分ける
結論から言うと、Pythonのマルチスレッド機能を使うことで分けることができます。
では、順に説明していきましょう。
まずは、完成像の共有です。
ボタンを押すことで現在時刻を取得するアプリがあり、ここに重い処理を加えます。
具体的には、時刻取得の際に何らかの重い処理としてsleepメソッドを追記しました。
マルチスレッド化する前のソースコードは下記となります。
#:kivy 1.11.1
MainScreen:
BoxLayout:
orientation: 'vertical'
size: root.size
Label:
id: view_label
text: '-'
Button:
text: 'Get Time'
on_press: root.button_clicked()
import datetime
import time
from kivy.app import App
from kivy.uix.widget import Widget
class MainScreen(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def button_clicked(self):
time.sleep(3) # 重い処理
self.ids.view_label.text = str(datetime.datetime.now())
class TestApp(App):
def __init__(self, **kwargs):
super(TestApp, self).__init__(**kwargs)
self.title = 'Window Name'
if __name__ == '__main__':
TestApp().run()
実行してみると、ボタンを押してもユーザーにはフィードバックが何もなく、3秒後に突然ボタンとラベルの表示が切り替わることと思います。
これでは、ユーザビリティ的に良くないですよね。
理想としては、ボタンを押したらボタンのフィードバックがあり、処理が完了したらラベルの表示が更新されるような、ボタンと処理の動作が分かれるていることが望ましいです。
そこで、マルチスレッドを使って、ボタンと処理を分けました。
改修後のpythonファイルは以下の通りです。
(Kivyのコードには変更ありません)
import datetime
import time
import threading
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
class MainScreen(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def update(self, dt):
self.ids.view_label.text = self.view_label_text
def button_clicked(self):
thread = threading.Thread(target=self.process)
thread.start()
def process(self):
time.sleep(3)
self.view_label_text = str(datetime.datetime.now())
Clock.schedule_once(self.update)
class TestApp(App):
def __init__(self, **kwargs):
super(TestApp, self).__init__(**kwargs)
self.title = 'Window Name'
if __name__ == '__main__':
TestApp().run()
このように記述することで、ボタンを押下した時のフィードバックは即時に反映しつつ、3秒後に時刻が表示されることと思います。
前は、button_clicked関数内に処理をまとめていましたが、処理はprocess関数に分けました。
button_clicked関数が呼ばれた時は、process関数のスレッドを作成して開始するだけにとどめています。
こうすることによって、ボタン押下と時刻取得の処理を切り分け、メインスレッドであるbutton_clicked関数は即時に終わらせることができるようにしました。
process関数の処理完了時には、updateメソッドをClockクラスを使って呼び出しています。
ClockというKivyのメインループとなっているクラスから呼び出さないと、値が更新されないので注意しましょう。
取得した時間情報の受け渡しは、self.view_label_textで受け渡ししていますが、update関数に引数を指定する形でも問題ありません。お好みで。
補足
ボタンのFBはon_pressで呼び出したメソッドの完了時
上記実行していただけたら気づくと思うのですが、ボタンのフィードバックはkvファイルのon_pressで指定したメソッドの完了時になります。
したがって、ボタンをフィードバックを簡潔させるためには、そのメソッドを完了させる必要があります。
マルチスレッドは、その名の通り複数のスレッドで動作させます。
thread.start()を実行すると、メインスレッドはthread.start()の完了を待たずにそのまま次のステップに進みます。
こうすることによって、on_pressで呼び出したメソッドを完了することができます。
マルチスレッドを使って処理を分担することは、Kivy公式のProgramming Guideにも記載されています。
Non GUI Operations that can be deferred to a different thread.と明記されています。
値の更新だけでは画面に表示されない
上記のソースコード、わざわざupdate()を用意しています。
一度、update()をコメントアウトして実行してみて欲しいのですが、update()がないと表示される値は更新されません。(変数としての値は更新されます。)
これも、on_pressで呼び出したメソッド終了時はプログラムが自動的に値の更新をみていると思います。
しかし、マルチスレッドで処理を分けていると、プログラム側は値の更新を拾うことができません。
したがって、update()を用意してあげて、Tread完了時に値の更新が完了したことを知らせてあげる必要があります。
Python学習におすすめの教材
Pythonについて、しっかりと基礎から学びたい、実践的なスキルを身につけたいと考えている方向けに、おすすめの学習教材を紹介します。
これらの教材は、実際に僕がPythonの学習に使った教材になっています。
現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル
現役シリコンバレーエンジニアが教えるPython入門!応用では、データ解析、データーベース、ネットワーク、暗号化、並列化、テスト、インフラ自動化、キューイングシステム、非同期処理など盛り沢山の内容です!
ベネッセが運営する動画学習サイトUdemyで視聴することができる動画教材です。
(セールの時には¥1,500程度で買えます!)
Pythonは自由度が高くて雑に書いても動きますが、その分コードスタイルがおろそかになりがちです。
正しいコードスタイルを身に着けておかなければ、Pythonプログラマとして活躍することはあり得ません。
この教材は、グローバルスタンダードなコードスタイルを正しく学びながら、アルゴリズムやサーバー構築など幅広くPythonの活用方法を学習することができます。
Pythonの学習で間違えたくない方や、趣味レベルのスキルから一歩抜け出したい方におすすめです。
エキスパートPythonプログラミング改訂2版 Pythonプログラミングのベストプラクティスを伝授
本書は、Pythonを使って仕事をしている開発者が普段どのようなツールやテクニックを用いて仕事をしているのか、また開発者が実際に現場で用いているベストプラクティスについて解説した書籍です。本書を読むことで、先進的なPythonプログラマが日常的に使用している開発ノウハウを学ぶことができます。
こちらの書籍は、様々なユースケースに対するベストプラクティスを学習することができる書籍です。
Python2系と3系の違いについてもよく取り上げられますので、実質どちらのバージョンにも対応しているといえます。
Pythonをうまく使いこなせていない気がする方、初心者の殻を破りたい方におすすめです。