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

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

新規登録して質問してみよう
ただいま回答率
85.35%
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

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

Q&A

解決済

2回答

2178閲覧

tkinterで動くボタンを押すゲームを作りたいけれど、threadがうまく動かない

usagitobi

総合スコア21

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

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

0グッド

0クリップ

投稿2020/08/26 15:17

編集2020/08/26 15:26

前提・実現したいこと

tkinterで動くボタンを押すゲームを作りたい

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

Traceback (most recent call last): File "C:\Users", line 47, in <module> th_1.start() AttributeError: 'NoneType' object has no attribute 'start'

該当のソースコード

python

1from tkinter import ttk 2import tkinter 3import threading 4import random 5 6class Move(threading.Thread): 7 def __init__(self,text,flag_x=True,flag_y=True,x=random.randint(0,400),y=random.randint(0,300)): 8 self.flag_x,self.flag_y = flag_x,flag_y 9 self.x,self.y = x,y 10 self.text = text 11 self.button = tkinter.Button(master=root,text=text,foreground="red",command=self.button_press) 12 # ボタンを押したときの処理 13 def button_press(self): 14 self.button.pack_forget() 15 lb = tkinter.Label(master=root,text=self.text,font=("MS ゴシック",50),fg="blue",bg="red") 16 lb.place(x=100,y=100) 17 # 実行 18 def run(self): 19 # 画面外に出ないように調節 20 if(self.flag_y==True): 21 self.y-=1 22 else: 23 self.y+=1 24 if(self.y==0): 25 self.flag_y=False 26 elif(self.y+self.button.winfo_reqheight()==300): 27 self.flag_y=True 28 29 if(self.flag_x==True): 30 self.x-=1 31 else: 32 self.x+=1 33 if(self.x==0): 34 self.flag_x=False 35 elif(self.x+self.button.winfo_reqwidth()==400): 36 self.flag_x=True 37 # ボタンをコンマ4秒ごとに配置 38 self.button.place(x=self.x,y=self.y) 39 self.button.after(4,lambda: self.run()) 40 41# ウィンドウの情報 42w,h="400","300" 43root=tkinter.Tk() 44ttk.Style().configure("TP.TFrame", background="snow") 45f=ttk.Frame(master=root,style="TP.TFrame",width=w,height=h) 46f.pack() 47# TODO/動くボタンをいくつか作成 48if __name__ == "__main__": 49 th_1 = Move("a").run(); 50 th_2 = Move("b").run(); 51 th_1.start() 52 th_2.start() 53root.mainloop() 54

試したこと

threadについてインターネットで学んだ。

補足情報(FW/ツールのバージョンなど)

python3のIDLEです。

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

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

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

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

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

guest

回答2

0

ベストアンサー

すでに回答が出ていますが、ちょっとだけ補足

threading.Threadから派生しているので、startを行なった時点でスレッドが開始されます。明示的にrunを呼ぶ必要がありません。
ですので、すでに回答があるように、オブジェクト生成とstartを分け、以下のようにするのが良いかと思います。

python

1 th_1 = Move("a") 2 th_2 = Move("b") 3 th_1.start() 4 th_2.start() 5 6 root.mainloop() 7 # 終了処理もきちんと 8 th_1.join() 9 th_2.join()

ただ、startを呼ぶと、RuntimeError: thread.__init__() not calledとエラーが出るので、エラーに従い、オーバーライドしたコンストラクタ内で、呼び出してあげる必要があります。(これも回答済みですが)

また、上記を修正して動かすと、期待した動作としては、2つのオブジェクトaとbが表示され、ランダムにウィンドウ内を動くはずであるが、実際に動作させると、一つのオブジェクトのみが表示されることになります。

これは、コンストラクタのデフォルト引数に問題があるからです。

python

1def __init__(self,text,flag_x=True,flag_y=True,x=random.randint(0,400),y=random.randint(0,300)):

上記のようなコードを記載すると、xやyに全く同じ値が設定されます。
これは、実行時にrandom.randintが呼び出されるのではなく、関数の定義時に呼び出され、実行時にはその際の値が設定されて呼び出されるpythonの仕様だからです。

本来の動作をしたいのであれば、xやyにデフォルトでNoneを設定し、関数内部でNoneの場合に値を設定する必要があります。

python

1def __init__(self,text,flag_x=True,flag_y=True,x=None,y=None): 2 3 threading.Thread.__init__(self) # 追加 4 if x==None: 5 x=random.randint(0,300) 6 if y==None: 7 y=random.randint(0,300) 8 9

投稿2020/08/27 01:27

編集2020/08/27 01:30
t_obara

総合スコア5488

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

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

usagitobi

2020/08/27 11:12

こんなに丁寧に教えていただきありがとうございます! 無事正常に動きました。
guest

0

以下のコードで正常動作を確認しました。

from tkinter import ttk import tkinter import threading import random class Move(threading.Thread): def __init__(self,text,flag_x=True,flag_y=True,x=random.randint(0,400),y=random.randint(0,300)): threading.Thread.__init__(self) self.flag_x,self.flag_y = flag_x,flag_y self.x,self.y = x,y self.text = text self.button = tkinter.Button(master=root,text=text,foreground="red",command=self.button_press) # ボタンを押したときの処理 def button_press(self): self.button.pack_forget() lb = tkinter.Label(master=root,text=self.text,font=("MS ゴシック",50),fg="blue",bg="red") lb.place(x=100,y=100) # 実行 def run(self): # 画面外に出ないように調節 if(self.flag_y==True): self.y-=1 else: self.y+=1 if(self.y==0): self.flag_y=False elif(self.y+self.button.winfo_reqheight()==300): self.flag_y=True if(self.flag_x==True): self.x-=1 else: self.x+=1 if(self.x==0): self.flag_x=False elif(self.x+self.button.winfo_reqwidth()==400): self.flag_x=True # ボタンをコンマ4秒ごとに配置 self.button.place(x=self.x,y=self.y) self.button.after(4,lambda: self.run()) # ウィンドウの情報 w,h="400","300" root=tkinter.Tk() ttk.Style().configure("TP.TFrame", background="snow") f=ttk.Frame(master=root,style="TP.TFrame",width=w,height=h) f.pack() # TODO/動くボタンをいくつか作成 if __name__ == "__main__": th_1 = Move("a") th_2 = Move("b") th_1.run() th_2.run() th_1.start() th_2.start() root.mainloop()

まず、run関数は戻り値がないため、そのままだとNoneを返します。よって、AttributeError: 'NoneType' object has no attribute 'start'が発生します。

これを回避するには、一度Moveクラスでth_1を定義してからrun関数を実行する必要があります。

また、th_1.run()が正常に実行されても、このままだとth_1.start()で以下のようなエラーが発生します。

RuntimeError: thread.__init__() not called

このエラーは、クラスの__init__()にthreading.Thread.__init__(self)を入れることでなくせます。

投稿2020/08/27 00:12

amahara_waya

総合スコア1029

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

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

usagitobi

2020/08/27 11:15

回答ありがとうございます! すごくわかりやすかったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問