Pythonでclassを使う: 設計編(1) 関数とクラス
はじめに
この記事は、(主に研究等で)pythonのプログラムを書く時に、どのように設計すれば良いかについて論じながら、クラスを導入する時に生じるメリットについて述べていきます。
内容
単一の処理の記述
任意の処理Aの記述の際は、非常に簡単な処理を除けば、処理Aに対応する関数を記述すると良いです。
関数のメリットは以下が考えられます。
- 処理に関数名として処理に名前をつけられるため、可読性が高い
- 1により余計なコメントアウトを書く必要はない
- 汎用化させやすい(同じ処理を何度も書く必要がない)
- docstringで記述した関数のコメントをコードで呼べる
- 処理の引数に型アシストを付与できる
- テストの記述が簡単
- 後のパッケージ化が簡単
番号が大きくなるにつれてメリットは薄いかもしれません。
一応4について補足しておきますが、関数名+?
で説明を呼ぶことができます。
In [1]: def add_2things(a,b):
...: """
...: 引数a,bを足す関数
...: """
...: return a+b
...:
In [2]: add_2things?
Signature: add_2things(a, b)
Docstring: 引数a,bを足す関数
File: ~/tmp/<ipython-input-1-e1908036e578>
Type: function
In [3]:
またGithubなどの編集アシスト機能などでもdocstring
を表示することもできます。
複数の処理の記述
さて次は、あるobjectmodel
に対して、処理A,Bを施すことを考えます。
実装をする場合は、引数をmodelとして関数A,B(model_func_A,model_func_B
)を記述すると以下のようになります。(記述例1
)
記述例1
def model_func_A(model):
#処理A
def model_func_B(model):
#処理B
関数名は、model
に対して処理を行うことを明示的に表すため、model*
としました。
ここから本記事の本題に移ります。
筆者のオススメは、同一のobjectに対して複数の処理を記述する場合は記述例1
と書くところを以下のように記述することです。
class ModelFuncClass:
def __init__(self, model):
self.model = model
def func_A(self):
#処理A self.modelでmodelを呼び出す
def func_B(self):
#処理B
関数で記述した場合(記述例1
)との違いは以下です。
- 複数の関数がクラスにまとまっている
- 引数を
self
で保持することで、メソッドを呼び出す時に引数を減らせている - 関数名を短縮できる(modelに対する処理であることはクラス名からわかるので)
複数の処理の記述(共通の引数)
class化のメリットは他にもあります。
例えば処理A,Bを施す際に、処理Xの出力が必要である場合を考えます。
関数で記述した場合は、新たに関数X(model_func_X
)を導入して、以下のように書けます。
def model_func_X(model):
#処理X
return x
def model_func_A(model,x):
#処理A
def model_func_B(model,x):
#処理B
この記述では、関数X(model_func_X
)の出力をmain関数側で保持し、関数A,B(model_func_A,model_func_B
)に渡す必要があります。
ここでclassを用いる以下のようになります。
class ModelFuncClass:
def __init__(self, model):
self.model = model
self.x = None #初期値があれば書くと良い
def func_X(self):
#処理X
self.x = x
def func_A(self):
#処理A self.model, self.xでそれぞれmodel,xを呼び出す
def func_B(self):
#処理B
関数との大きな違いは、関数Xの出力をmain関数側で保持する必要がないということです。
さらに(当たり前かもしれませんが、)model
の処理に関わる変数をclassで保持できるので、可読性が高いです。
また__init__
においてself.x = None
と記述しているのは、保守性を高めるため(self.x
が定義されていないというエラーを出ないようにするため)、可読性を高めるため(クラスが保持する変数が__init__
でわかる)です。
また関数Aで以下のようにself.x
がない時だけ関数Xを呼ぶのに役立ちます。
(余計なflagを使わないで済む&self.func_X
を何度も呼ばなくて済む)
# classのメソッド
def func_A(self):
if not self.x:
self.func_X()
#処理A
また関数A(メソッドA)の処理において、途中計算の結果を別のメソッドで使用したいと思った場合、
classで書いておけば、インスタンス変数として定義するだけで済みます。
つまり途中計算の結果をスコープの外で使うことができるということです。(自分一人のシステムなら良いが、多用すると訳がわからなくなるので注意)
# classのメソッド
def func_A(self):
self.y = (なんらかの途中計算の結果) # 別のメソッドでself.yとして使用可能
まとめ
次回はclassの設計について述べる予定です。
Discussion