質問1:「コンストラクタでメンバ変数を定義する」というのは、pythonの慣習の様な物でしょうか。それとも、コンストラクタを使わず宣言・定義を行うとバグの温床になる等、何か理由があっての事でしょうか。
AのコードとBのコードは「別物」です。Aのコードではval_a
, val_b
はインスタンスの属性になります。Bのコードではクラスの属性になります。
他の言語では違うかもしれませんが、Pythonはクラスもインスタンスです(type
クラスのインスタンスと位置づけられます)。
日本語でうまく表現するのが難しいのですが、この違いです。Pythonはオブジェクトに動的に好きな名前の属性を追加できる言語なのに注意してください。
python
1class Hoge:
2 pass
3
4# A
5h = Hoge()
6h.a = 10
7
8# B
9Hoge.a = 10
もっと端的にいえば、Aは「インスタンス変数」、Bは「クラス変数」という言葉でそれぞれ呼ばれます。
質問2:書き方Aと書き方Bでは、クラスの振る舞いは同一ですが、内部の動きに違いは生じるのでしょうか。
厳密に言えば、振る舞いは同一ではありません。
とりあえず、わかりやすくするために以下のように書き直すことにします。
python
1 def calc(self):
2 self.val_a = self.val_a + 10
3 self.val_b = self.val_b + 1
結論を先に行ってしまうと、左辺で代入している対象はどちらもインスタンス変数(インスタンスの属性)です。
ただし、右辺のself.val_a
, self.val_b
で「何」にアクセスしているかが問題です。
self
の属性からはクラス変数にもインスタンス変数にもアクセスできる
- 同名で両方あるときはインスタンス変数の方が優先順位が高い
という規則があり、そのためAの方ではインスタンス変数、Bの方ではクラス変数へのアクセスになります。
そしてPythonでは属性への代入で新たな属性(ここではインスタンス変数)を作ることができます。よって、A
の方はもともとあったインスタンス変数を書き換えるのですが、B
ではcalc
が呼び出された瞬間に新規にクラス変数と同名のインスタンス変数を作っていることになります。
といってもまあ結果論としては同じように見えてしまう例ですね。
immutableなオブジェクトを束縛した変数の挙動、累積代入文、クラス変数とインスタンス変数の優先順位、属性への代入など、Pythonのわかりづらいところが複数重なっているとても厄介な例です。もう少し単純なものを見ておきましょう。mutableのlist
にして要素を追加してみます。
python
1class hoge_A:
2 def __init__(self):
3 self.lst = []
4
5 def append1(self):
6 self.lst.append(1)
7
8class hoge_B:
9 lst = []
10 def append1(self):
11 self.lst.append(1)
12
13h1 = hoge_A()
14h1.append1()
15print(h1.lst) # [1]
16
17h2 = hoge_A()
18h2.append1()
19print(h2.lst) # [1]
20
21
22h1 = hoge_B()
23h1.append1()
24print(h1.lst) # [1]
25
26h2 = hoge_B()
27h2.append1()
28print(h2.lst) # [1, 1]
29
30
わざわざクラス変数と同名のインスタンス変数に代入を行わなければ、基本的にはクラス変数が見える状態のままであることに注意してください。
細かい解説は不要かと思いますが、クラス変数は全インスタンスで共有されますので、最後の行のような結果になります。
self
からクラス変数へアクセスするのは、比較的挙動がわかりづらく、混乱を招きがちなのであまり推奨されていない気がします。
明示的にクラス変数へアクセスしたい場合は、type(self).val_a
などがいいでしょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/04/02 12:08