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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

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

Q&A

解決済

2回答

4718閲覧

Pythonで処理を分けるスマートな書き方を教えてください。(Tkinter)

decoyxyz

総合スコア13

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

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

0グッド

1クリップ

投稿2019/05/19 20:58

編集2019/05/20 13:38

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

一応解決となりましたので、様子をみてベストアンサーを決定してクローズとさせていただきます。

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

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

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

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

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

y_waiwai

2019/05/19 23:22

あなたの言うスマート、というのが理解できません。 具体的にどうしたいんでしょうか。 関数を増やさなければ損でいい? 名前を消費しなければそんでいい? ネストを浅くすればそんでいい? 何を目指してるんでしょう
decoyxyz

2019/05/20 07:09

抽象的な質問で申し訳ございません。 2019/05/20の夜に質問を追記致します。
guest

回答2

0

ベストアンサー

ご質問を拝見する限り質問者さんが例として挙げておられるコードは単純化しており、質問意図は(例には書かれていない)他の色々なアプリケーションの機能において如何に個々の機能間の依存関係(例えばネームスペース内での属性名の衝突など)を抑えるかについてのPythonにおける方法論だと思います。

例ではA_message, B_messgeは共通化できるものになってしまってますが、これはたまたまそういう例としてしまっただけで本来は別々の実装のものを想定しておられるのではないでしょうか。

さてPythonにおいて機能を分割するのに使える実装の置き所には

・パッケージ/モジュール
・クラス
・関数

があります。どこに置くかは再利用性や機能の規模によりケースバイケースなわけですが、Pythonの場合、質問者さんがトライしておられるようにクラスと関数の定義場所はかなり柔軟でありほとんどどこにでも定義することができます。

自分にはスマートの基準がはっきりあるわけではないですが

  • クラスや関数の入れ子はせいぜい0か1にした

それ以上になるとスマートかどうか以前に把握しづらくなる。

  • 似たようなパターンを複数個所に書くのは基本的にいやだ

前述したとおり本件においては質問者さんの興味はここにはないと思います。

  • 互いに関連のない雑多なインスタンス変数が一つのクラスにごちゃごちゃあるのはいやだ

言い換えれば機能の分割のしかたです。ここが質問者さんの興味の焦点だと思います。

パッケージやモジュールを分割するとなると相当まとまりのある大きな機能をイメージします。アプリケーションレベルですと、例えば「キャンバスの上に様々な図形を管理し、それらをアニメーションできるような機能」なんてものなら他のモジュールに独立したクラスとして置きたくなります。

本件の「一定時間ごとに自動的に現在時刻を表示するラベル」を例にとるとパッケージやモジュールに置くようなものではなさそうな印象で、クラスとしてなら悪くはない気がします。ただしアプリケーションが持つ単一の機能(複数個所に出現しないという意味)としてこれをクラスに定義するのはどうでしょう?ちょっとやりすぎな香りがします。しかしながらそういう機能がアプリケーションの複数に出現するならクラスとすることは自然に感じます。

もしクラスにするとしてその置きどころですが・・・

Python

1class App: 2 def m1(self, ...): 3 class LocalClass1: 4 def m2(_self, ...): 5 ... _selfをアクセス ... 6 ... selfをアクセス ... 7 8 class LocalClass2: 9 def m2(self, ...): 10 ... selfをアクセス ... 11 12class FunctionInApp: 13 def m2(self, ...): 14 ... selfをアクセス ...

LocalClass2がFnctionInAppと違う点はLocalClass2がモジュールのグローバルな名前空間上ではApp.LocalClass2となり他のグローバルな名前と違う空間にあることだと思います。しかし逆に言えば違いはそこにしかなく、LocalClass2がAppの文脈で何かFuncitionInAppより有利なアクセスができるかというとそういうことはありません。一方LocalClass1はAppクラスのm1メソッドの実行の文脈(ローカル変数)をアクセスできます。しかしながらこのクラスは一時的にしか存在しないわけなので他の機能との共用はもはやできません。

結論を言いますともしアプリケーションの機能を分割するなら「アプリケーションの機能の定義の置き所である__main__モジュール」のトップレベルにFunctionInAppを置くのが自然(少なくとも一つの方法)に感じます。必ずいつもそうという意味ではなく多くの場合そこでいいんじゃないかというほどの意味です。本当に局所的にしか使わない機能ならLocalClass1あるいは同じ位置に置いた関数あるいはAppのメソッドでよいと思います。


コメントしてはみたものの「スマートかどうか」という観点にマッチした回答になってないような気がします。いろいろ思うところがあるのですが回答が発散しそうに思えたので止めました。中途半端感が激しい回答に終わってしまったような気がします...

投稿2019/05/20 05:13

KSwordOfHaste

総合スコア18394

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

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

decoyxyz

2019/05/20 07:08

ご回答ありがとうございます! 私が求めていた回答です。 力不足により分かりにくい説明となってしまいましたが、汲み取って頂き感激です。 クラスにより、機能の切り分けを行いたいと思います。 ありがとうございました。
guest

0

必要以上に処理を分割するのがそもそもスマートではないというケースのような。

↓じゃ駄目ですか?

python

1import tkinter 2import datetime 3 4class Test: 5 def __init__(self): 6 self.root = tkinter.Tk() 7 8 frameA = tkinter.Frame(self.root) 9 frameA.pack() 10 frameB = tkinter.Frame(self.root) 11 frameB.pack() 12 13 self.A_message = tkinter.Label(frameA, text="TestAAAA") 14 self.A_message.pack() 15 16 self.B_message = tkinter.Label(frameB, text="TestBBBB") 17 self.B_message.pack() 18 19 self.update() 20 21 self.root.mainloop() 22 23 def update(self): 24 self.A_message["text"] = str(datetime.datetime.now()) 25 self.B_message["text"] = str(datetime.datetime.now()) 26 self.root.after(1000, self.update) 27 28Test() 29

投稿2019/05/20 01:18

hayataka2049

総合スコア30933

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

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

decoyxyz

2019/05/20 06:55

ご回答ありがとうございます。 説明不足で申し訳ございません。 実際のGUIでは、 root > 20個のframe※ > それぞれのframeに50程度のウィジェット となっており、すべてをinitに記入すると膨大な長さになります。 ※メニュー、トップページ、それぞれの機能のページなど 後日、例のコードをもっと分かりやすく更新致します。
hayataka2049

2019/05/20 07:08

そこまで規模が大きいとそもそも「tkinterで書くべきか」というあたりから見直すべきかもしれません。 pyqtとかならツールで画面を作って読み込むということもできます。
decoyxyz

2019/05/20 13:39

tkinterで書くべきではないかもしれないというのは重々承知しておりますが、過去のコードとの兼ね合いもありPyqtに移行するのは難しい状態です・・・
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問