質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
並列処理

複数の計算が同時に実行される手法

while

Whileは多くの言語で使われるコントロール構造であり、特定の条件が満たされる限り一連の命令を繰り返し実行します。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

6396閲覧

PySimpleGUIでスレッド処理にてカウント実行しながらwindowの画像表示をループさせたい。

dendenmushi

総合スコア98

並列処理

複数の計算が同時に実行される手法

while

Whileは多くの言語で使われるコントロール構造であり、特定の条件が満たされる限り一連の命令を繰り返し実行します。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/12/07 15:14

編集2021/12/08 11:34

前提・実現したいこと

PySimpleGUIでスレッド処理にてカウント実行しながらwindowの画像表示をループさせたい。

①下記デスクトップアプリに横棒の画像を表示させているのが初期状態で、Startをクリックすると、パラパラアニメとして横棒が下に移動していきます。(1~5.pngを用意しています。)

②上記パラパラが動きつつ、別スレッドでカウントwhileメソッドが動き続けます。

イメージ説明

環境

OS:windows10
language:Python3.7
Library:PySimpleGUI,threading

発生している問題・エラーメッセージ

トラブル1:スレッドが正常に実行できない。

イメージ説明

AttributeError: 'str' object has no attribute 'window'

該当のソースコード

python

1import threading 2import time 3import sys 4import PySimpleGUI as sg 5 6def play_count(fname): 7 str_num = 0 8 while True: 9 str_num += 1 10 time.sleep(1) 11 print(str_num) 12 13def play_anime(self): 14 n_count = 0 15 n_times = 0 16 while True: 17 # time.sleep(2) 18 # n_count += 3 19 # print('aaaaaaaa:', n_count) 20 # ここはなんとか1.png 2.png 3.png 4.png 5.pngを代入しようとした 21 if n_count == 5: 22 n_times += 1 23 if n_count > 5: 24 if n_count % 5 == 0: 25 n_times += 1 26 n_count += 1 27 n_count_last = n_count - 5 * n_times 28 29 print('./' + str(n_count_last) + '.png') 30 31 self.window['-image_canvas-'].Update(filename = './' + str(n_count_last) + '.png') 32 33 34 35if __name__ == '__main__': 36 #ウインドウの表示、設定 37 sg.theme('BluePurple') 38 layout = [ 39 [sg.Text('処理の実行、変更、停止を行います:'),sg.Text(size=(15,1), key='-OUTPUT-')], 40 [sg.Button('Start'),sg.Button('exchange')], 41 [sg.Image('./1.png', key='-image_canvas-')] 42 ] 43 window = sg.Window('Pattern 2B', layout) 44 45 #スレッド処理のインスタンス生成 46 # = Receive(window) 47 def startEvent(event):#スタートボタン押下時の処理 48 #r.ROOP = True 49 #r.start() 50 51 DATA = "arg" 52 thread3 = threading.Thread(target=play_count, args=[DATA]) 53 thread4 = threading.Thread(target=play_anime, args=[DATA]) 54 55 thread3.start() 56 thread4.start() 57 58 thread3.join() 59 thread4.join() 60 61 62 n_count = 0 63 n_times = 0 64 #rotation_flag = False 65 while True: 66 event , values = window.read() 67 #ボタンの処理内容 68 if event == 'Start': 69 window['-OUTPUT-'].update('実行中') 70 # rotation_flag = True 71 startEvent(event) 72 73 if event == 'exchange': # UP ボタンを押したら画像切り替え 74 75 if n_count == 5: 76 n_times += 1 77 78 if n_count > 5: 79 if n_count % 5 == 0: 80 n_times += 1 81 82 n_count += 1 83 n_count_last = n_count - 5 * n_times 84 window['-image_canvas-'].Update(filename = './' + str(n_count_last) + '.png') 85 86 # if rotation_flag == True: 87 # time.sleep(3) 88 # print('rotation_flag if 内部') 89 # if n_count == 5: 90 # n_times += 1 91 92 # if n_count > 5: 93 # if n_count % 5 == 0: 94 # n_times += 1 95 96 # n_count += 1 97 # print('n_count:', n_count) 98 # n_count_last = int(n_count) - 5 * n_times 99 # print('n_count_last:', n_count_last) 100 # #self.window['-image_canvas-'].update(filename = './2.png') 101 # window['-image_canvas-'].Update(filename = './' + str(n_count_last) + '.png')

試したこと

ソースコードの一番下の場所で今コメントアウトになっているところなのですが、そこでwindowを更新すればよいのではと思い試しました。当初、play_animeメソッド内で windowのupdateをいくらしても、windowsが更新されないのであれば、意味がないのだと思い、この更新メソッドを一番下に記載しました。

ところが、startEventメソッドに行き、スレッド実行2つが始まったら、もはや今のソースの箇所は読み込まれず、結局実現できませんでした。何かアドバイス頂けないでしょうか。よろしくお願いいたします。

2021/12/8 20:33 追記

ppaulさんの方法を試したところ確かに該当のエラーはなくなりました。
新しいエラーが出てしまいました。
イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

AttributeError: 'str' object has no attribute 'window'

Google翻訳すると

Translate

1AttributeError: 'str'オブジェクトに属性 'window'がありません

です。

これが起こるのは、

python

1 DATA = "arg" 2 thread3 = threading.Thread(target=play_count, args=[DATA])

でplay_countに与えた引数DATA、つまり"arg"はwindowという属性を持っていないからです。

python

1 self.window['-image_canvas-'].Update(filename = './' + str(n_count_last) + '.png')

python

1 window['-image_canvas-'].Update(filename = './' + str(n_count_last) + '.png')

に変更すればエラーは出なくなるでしょう。
期待する動きをするかどうかは、別問題です。

投稿2021/12/07 23:09

ppaul

総合スコア24666

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dendenmushi

2021/12/08 11:35

該当エラーはなくなりましたが次なるエラーが出てしまいました。もう少し熟考してみます。ありがとうございました。
guest

0

ベストアンサー

PySimpleGUI で、GUI + バックグラウンドで他の操作をする際のサンプルコードです。

(下URLのデモの説明)サブスレッドからは、window.write_event_value で "-THREAD-" というイベントを通知して、
メインのイベント処理で "-THREAD-" イベントを受け取り処理します。
イベント名を識別する文字列は、任意なので何でもよいです。
-大文字- としてるのは、PySimpleGUIでの命名の慣習。

GUIでのプログラミングは大抵、イベント駆動型と呼ばれ、
イベントや描画の更新を行うメインとなるループ処理(イベントループ)を持ちます。

GUI関連の処理は、必ずこのイベントループ内から呼び出される必要があります。
PySimpleGUIのバックエンドがtkinterの場合は、別スレッドからの操作でも大丈夫なことも有りますが、
意図しない挙動になることもある為、お勧めしません。他のGUIライブラリでは大抵エラーです。

解消法としては、(追記・補足: スレッドを使う場合は) 上記のサンプルの様に
サブスレッドからは通知のみを行い、メインスレッド側でGUI関連の処理が行われるような構成にして下さい。

他の案で、サブスレッド側で行う処理が時間の掛かる処理でない場合は、タイマーイベントを使う方法もあります。
処理内容次第ですが、用途がアニメーションのみであれば、タイマーの方が適切な場合もあり。


当初、play_animeメソッド内で windowのupdateをいくらしても、windowsが更新されないのであれば、意味がないのだと思い、この更新メソッドを一番下に記載しました。

GUI が更新されるのはイベントループ内、PySimpleGUI の場合は window.read の内部です。
明示的に更新する方法もありますが、サブスレッドからのGUIの直接操作自体を回避した方が良いです。
上述した方法で、「GUIの操作はメインスレッドからのみ」とすると、必然的に解消されるはずです。

※ 厳密には、GUIのイベントループを動かしてるスレッドで。大抵の場合は、メインスレッド。


追記: 他の問題点

  • def play_anim(self): ... この関数はトップレベルで宣言されてます、

 クラス内ではないのでメソッドを想定した使いかたは出来ません。

  • args=[DATA] ... 呼び出される側の関数で想定されてる値と異なります。
  • thread.join ... join はスレッドの終了を待ちます。

 イベントループ内で使うと、イベントループは停止しスレッド側に処理が移るので、
GUIイベントが処理されない → ウィンドウが応答なしの原因になります。

現状のコードの問題点ですが、スレッドの利用方法から見直す必要があるので、
エラーを修正して期待通りに動くとはいきません。

投稿2021/12/08 04:07

編集2021/12/10 07:28
teamikl

総合スコア8664

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dendenmushi

2021/12/08 11:37 編集

参考サイトもありがとうございます。はじかのコードからまず試させて頂きます。
teamikl

2021/12/10 07:11 編集

自作ライブラリの紹介になるのですが、アニメーション用途のみであれば (※重要: 裏で時間の掛かる処理は行わない場合) https://libraries.io/pypi/gentimer 適用方法: 実行する関数の time.sleep を yield に置き換え、 thread の呼び出しの代わりにこのライブラリを使うだけで、 ほぼ意図する挙動になるはずです。 ---- アニメーション用途はGUIの提供するタイマーを使った方が適切です。但し、 GUIライブラリの提供するタイマーは指定時間後に実行というインターフェースなので、 コールバック形式でコードを書かないといけない。→ コードが複雑になり制御が追い難い thread/time.sleep で書くとループ文を使って解りやすいコードが書けるが、 今度はマルチスレッドでの排他制御といった、スレッド特有の課題が生じます。 ^-- ここが今回の質問で起こってる問題 (補足: 質問に提示のエラー自体ではなく、プログラムの設計的な問題です。 原則として、マルチスレッドでは、別スレッドからGUIを直接操作してはいけません。) 別言語・別ライブラリでも同種の問題はあって、非同期処理の為の構文を取り入れたりしてます。 このライブラリでは、その双方の欠点を解消する方法を tkinter に移植したものです。 Python のジェネレーターという機能を用いる事で、 yield の部分でイベントループへと処理を戻し、一定時間後に続きの処理を再開出来、 スレッドを用いた時のような解りやすいコードで、記述できるようになります。 (複数のバックエンドに対応してますが、 PySimpleGUI は、標準では tkinter がバックエンドです) ---- 時間の掛かる処理がある場合は、別スレッドにする方が適切です。 サブスレッドとメインスレッド間でののデータのやり取りは、 Long Operations - Multi-threading のコードを参考にして下さい。
teamikl

2021/12/10 12:02

画像での例ではありませんが、バックグラウンドで行う処理の実装サンプル・比較にどうぞ 1. タイマー(timeoutイベント)での実装。  イベントループ内で定期的な処理を行う場合に用いるイベント。 2. スレッドでの実装。<-- 質問のコードの方針で実装する場合、write_event_value の使い方  裏で重たい処理を行う場合。ファイル・データベース・ネットワーク操作等。 3. ジェネレーターで実装。<-- コメント内で提案  アニメーション等のGUIの操作が頻繁に入る場合に適した方法。 https://replit.com/@MiKLTea/sgGenTimerSample#main.py
dendenmushi

2021/12/12 04:39

ありがとうございます。確認いたします。
dendenmushi

2021/12/12 05:38

3ファイル動かしてみました。画像が表示されないのですが1から5.pngと文字は表示されていました。threadファイルにあるf1という表記も気になりました。もう少し実装みてみます。いつもありがとうございます。このloggingやthreadや値の受け渡しやクラス作成についてUdemyで講義して頂きたいなぁといつも思ってます。必ず購入します。。
teamikl

2021/12/12 15:45

画像ファイルを用意する代わりに、 テスト用にファイル名の文字列を渡すようにした為、意図した通りの挙動です。 > f1という表記も気になりました。 f"文字列 {式}" という文字列の中に変数を埋め込むフォーマット表記かな。 f-strings という Python3.6 から実装された書式です。 ---- アニメーションGIF を再生する方法。 tkinter(PySimpleGUIのバックエンド) では gif 形式のファイルの読込はサポートしてますが、 アニメーション再生はサポートされてないので、タイマーを用いて独自に再生します。 https://pysimplegui.trinket.io/demo-programs#/animation/animated-gifs 上記のサンプルは、10ms 間隔で高速に表示する画像を切替えてますが、 スライドショーの場合も考え方は同じで、タイマー(timeoutイベント)を利用する方法が使えます。 アニメーション用途であれば、スレッドよりもタイマーを用いる方法が適してます。 同じチュートリアルより PNG Image Viewer の例です。 https://pysimplegui.trinket.io/demo-programs#/examples-for-reddit-posts/png-image-viewer この例では、クリック時に次の画像を表示していますが、クリックイベントの代わりに、 タイマー(timeoutイベント)で一定時間で変わるように利用すれば、 ほぼ目的のものが実装できるはずです。 ---- > loggingやthreadや値の受け渡しやクラス作成について (アニメーション用途ではタイマーを使う方法をおすすめしますが、スレッドの理解が目的の場合) 「排他制御」や「同期キュー」といった、マルチスレッドにおける基礎の理解が不可欠です。 分量的に1からの解説は難しいので、掻い摘んで参考になる資料の紹介 PySimpleGUI のチュートリアルより、マルチスレッドの事例幾つか https://pysimplegui.trinket.io/demo-programs#/multi-threaded/window-perform_long_operation-method PySimpleGUI のマルチスレッドでの logging のデモ logging ライブラリはスレッドセーフなので、端末に出力する場合は Queue は不要ですが、 Queue の使い方の事例として、GUIにログを出力する場合。 https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Multithreaded_Logging.py GUIのマルチスレッドのプログラムでは、 前提として GUI関連の処理は、GUI のイベントループ内(メインスレッド側)で行わないといけないので、 同期キューを用いてスレッド間のデータをやりとりする手法が一般的です。 流れは、サブスレッド側でイベントを発生させ(キューにデータを入れる)、 メインスレッド側のイベント処理で受け取ります(タイマー等の定期イベントでキューから取り出す)。 PySimpleGUI では、write_event_value が担当し、内部ではQueueが使われています。 ヒント…多分この辺りが理解の鍵 - GUIプログラミング: イベント駆動、イベントループ ... 何故メインスレッド側でGUIの処理を行わなければならないか - マルチスレッド・プログラミング: 同期キューを用いたスレッド間通信 ... 解決手段の一つ 恐らく知りたい情報は、この辺りだと思います。マルチスレッド+GUIプログラムの注意点 - スレッドセーフでない関数はスレッドを跨いで利用してはいけない。  window.write_event_value や logging モジュールはスレッドセーフなので、  スレッドを跨いだ利用でも安全です。他は、記載がない限りスレッドセーフではありません。  (スレッドを跨ぐというのは、メインスレッドで生成してサブスレッドで使うような使い方) - ブロッキング処理 (time.sleep や thread.join、時間のかかるループ文、等) はイベントループ内で呼び出してはいけない → 「応答なし」の原因  他のブロッキング処理があると、GUIのイベントの処理が停滞し応答無しとなります。
dendenmushi

2021/12/15 12:02

いろいろな要素が組み合わさっていたのですね。熟読いたします。ありがとうござました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問