Pythonのインスタンス変数の書き方について、悩んでしまいました。
落ち着いて考えると打開策があるような気も致しますが、煮詰まってしまったので、ご教示お願い致します。
例えば、以下のコードでは、Aという処理とBという処理をメソッドに書き出しています。
Python
1class Test: 2 def __init__(self): 3 self.v = 10 4 self.setA() 5 self.setB() 6 while True: 7 self.addA() 8 self.addB() 9 10 def setA(self): 11 self.A = 10 12 13 def addA(self): 14 self.A+= self.v 15 16 def setB(self): 17 self.B = 10 18 19 def addB(self): 20 self.B += self.v 21
しかしながら、この書き方は、インスタンス変数AとBがinitの外に書き出されているため、あまりスマートではない気がします。
(例えinitの中に書いても、変数が増えていくたびにinitに追記していくのは苦痛です。)
また、メソッド内のすべての変数名に、どのメソッドに属しているような記号を付ける必要があります。
より具体的に本件で悩んでしまったのは、TkinterでGUIを作る際です。
2つのframeをrootとして、処理をメソッドに分けて書いています。
ボタンクリックによるイベント処理を書きやすくするために、極力self以外の引数は書かないようにしています。
Python
1import tkinter 2import datetime 3 4class Test: 5 def __init__(self): 6 self.root = tkinter.Tk() 7 frameA = tkinter.Frame(self.root) 8 frameA.pack() 9 frameB = tkinter.Frame(self.root) 10 frameB.pack() 11 self.drawA(frameA) 12 self.drawB(frameB) 13 self.update() 14 self.root.mainloop() 15 16 def update(self): 17 self.updateA() 18 self.updateB() 19 self.root.after(1000, self.update) 20 21 def drawA(self, root): 22 self.A_message = tkinter.Label(root, text="TestAAAA") 23 self.A_message.pack() 24 25 def updateA(self): 26 self.A_message["text"] = str(datetime.datetime.now()) 27 28 def drawB(self, root): 29 self.B_message = tkinter.Label(root, text="TestBBBB") 30 self.B_message.pack() 31 32 def updateB(self): 33 self.B_message["text"] = str(datetime.datetime.now()) 34 35 36Test() 37
この書き方では以下のデメリットがあると思います。
- 「self.B_label_XXX...」などと変数名の最初にメソッド固有の名前を付ける必要があり煩わしい。
- メソッド固有の名前にしているため、コードを使いまわしするには必ず変数名の変更が必要になる。
(ウィジェットの数が多くなると深刻です。)
- 名前空間を消費する。
そこで、辞書を宣言して、すべてのwidgetをその中に放り込んでしまうということを思いつきました。
Python
1import tkinter 2import datetime 3import sys 4 5class Test: 6 def __init__(self): 7 self.root = tkinter.Tk() 8 frameA = tkinter.Frame(self.root) 9 frameA.pack() 10 frameB = tkinter.Frame(self.root) 11 frameB.pack() 12 self.widget = {} 13 self.drawA(frameA) 14 self.drawB(frameB) 15 self.update() 16 self.root.mainloop() 17 18 def update(self): 19 self.updateA() 20 self.updateB() 21 self.root.after(1000, self.update) 22 23 def drawA(self, root): 24 key = sys._getframe().f_code.co_name[-1] 25 self.widget[("label", key)] = tkinter.Label(root, text="TestAAAA") 26 self.widget[("label", key)].pack() 27 28 def updateA(self): 29 key = sys._getframe().f_code.co_name[-1] 30 self.widget[("label", key)]["text"] = str(datetime.datetime.now()) 31 32 def drawB(self, root): 33 key = sys._getframe().f_code.co_name[-1] 34 self.widget[("label", key)] = tkinter.Label(root, text="TestBBBB") 35 self.widget[("label", key)].pack() 36 37 def updateB(self): 38 key = sys._getframe().f_code.co_name[-1] 39 self.widget[("label", key)]["text"] = str(datetime.datetime.now()) 40 41 42Test() 43 44
すべてのインスタンス変数の宣言がinitの中にあり、しかもコードの使いまわしが容易になりました。
しかし、変数名に変わるキーは「"label"」のように文字列になってしまっているため、IDEによるリファクタリングが効かなくなってしまいます。
クラス内にクラスを置いてみる・・・
import tkinter import datetime class Test: def __init__(self): self.root = tkinter.Tk() frameA = tkinter.Frame(self.root) frameA.pack() frameB = tkinter.Frame(self.root) frameB.pack() self.drawA = self.DrawA(self, frameA) self.drawB = self.DrawA(self, frameB) self.update() self.root.mainloop() def update(self): self.drawA.update() self.drawB.update() self.root.after(1000, self.update) class DrawA: def __init__(self, self_, root): self.root = root self.message = tkinter.Label(root, text="TestAAAA") self.message.pack() def update(self): self.message["text"] = str(datetime.datetime.now()) class DrawB: def __init__(self, self_, root): self.root = root self.message = tkinter.Label(root, text="TestBBBB") self.message.pack() def update(self): self.message["text"] = str(datetime.datetime.now()) Test()
・・・前の2つよりは改善された気がしますが、ネストが深くなってしまった気がします。
どうか、このようなケースでスマートに処理を分ける方法がありましたら、ご教示ください。
2019/05/20追記
ご回答ありがとうございました。
コードを簡略化しすぎていたため、分かりにくい質問となってしまったことをお詫びします。
ご回答を参考に、一旦、クラス内クラスを使うこととしました。
以下のコードは、「左のメニューのボタンをクリックすると、RobotAとRobotBのコントロールパネルを表示する」というサンプルコードです。
Python
1import tkinter 2import datetime 3 4class Test: 5 def __init__(self): 6 # ウインドウの作成 7 self.root = tkinter.Tk() 8 9 # 大まかなフレームを作成 10 self.root_menu = tkinter.Frame(self.root) 11 self.root_menu.grid(column=0, row=0, sticky=tkinter.NSEW) 12 self.root_contents = tkinter.Frame(self.root) 13 self.root_contents.grid(column=1, row=0, sticky=tkinter.NSEW) 14 15 # root_contentsに表示されるframe 16 self.select_contents = None 17 18 # root_contentsがリサイズされるようにする 19 self.root.columnconfigure(1, weight=1) 20 self.root.rowconfigure(1, weight=0) 21 22 # メニューを作成 23 self.root_menu_buttons = {} 24 for x in dir(Test): 25 if x[0:4] == "Draw": 26 item = tkinter.Button(self.root_menu, text=x[4:], command=self.selector(x[4:])) 27 item.pack() 28 self.root_menu_buttons[x[4:]] = item 29 30 # 子項目に渡すframeを作成 31 self.contents_frames = {} 32 for x in self.root_menu_buttons: 33 self.contents_frames[x] = tkinter.Frame(self.root_contents) 34 35 # 子項目をインスタンス化 36 self.contents_instance = {} 37 for x in self.root_menu_buttons: 38 exec("self.contents_instance[x] = self.Draw" + x + "(self, self.contents_frames[x])") 39 40 41 self.update() 42 self.root.mainloop() 43 44 def selector(self, select): 45 def func(): 46 # 表示されているものを隠す 47 if self.select_contents is not None: 48 self.select_contents.pack_forget() 49 print("隠す") 50 # 選択されたウィジェットを表示する。 51 self.contents_frames[select].pack(fill=tkinter.BOTH) 52 self.select_contents = self.contents_frames[select] 53 return func 54 55 def update(self): 56 for x in self.root_menu_buttons: 57 self.contents_instance[x].update() 58 59 self.root.after(1000, self.update) 60 61 # RobotAとRobotBは全く別の製品で処理の共通化は必要ない。 62 class DrawRobotA: 63 def __init__(self, self_, root): 64 self.root = root 65 tkinter.Label(root, text="RobotAのコントロールパネル").pack() 66 self.message = tkinter.Label(root, text="RobotA") 67 self.message.pack() 68 # 以下、RobotB固有の複数のwidgetが並ぶ 69 70 def update(self): 71 self.message["text"] = "RobotA" + str(datetime.datetime.now()) 72 73 class DrawRobotB: 74 def __init__(self, self_, root): 75 self.root = root 76 tkinter.Label(root, text="RobotBのコントロールパネル").pack() 77 self.message = tkinter.Label(root, text="RobotB") 78 self.message.pack() 79 # 以下、RobotB固有の複数のwidgetが並ぶ 80 81 def update(self): 82 self.message["text"] = "RobotB" + str(datetime.datetime.now()) 83 84Test() 85
一応解決となりましたので、様子をみてベストアンサーを決定してクローズとさせていただきます。
回答2件
あなたの回答
tips
プレビュー