下記のコードで test1 = TestBorg1()
でインスタンス化する際に{'state': 'second'}
がどこで保持され、二回目のインスタンス化であるtest2 = TestBorg1()
の際に{'state': 'second'}
を出力できるのでしょうか?
一方で、self.__dict__
にself._shared_state
を入れないケースのTestBorg2
の一回目のインスタンス化の際には、アウトプットを見る限り__dict__
に保持されていません。
なぜでしょうか?
当質問で一番気になっていることは、__dict__
に代入した値が一回目と二回目のインスタンス化の間でどうやって保持されているのか、が気になっています。また、その確認方法が気になっています。
コード
python
1class TestBorg1: 2 _shared_state = {} 3 4 def __init__(self): 5 print(f"self._shared_state1: {self._shared_state}") 6 self.__dict__ = self._shared_state 7 self.__dict__["state"] = "second" 8 9 10class TestBorg2: 11 _shared_state = {} 12 13 def __init__(self): 14 print(f"self._shared_state2: {self._shared_state}") 15 self.__dict__["val"] = self._shared_state 16 self.__dict__["val"] = "test" 17 18 19if __name__ == "__main__": 20 test1 = TestBorg1() 21 test2 = TestBorg1() 22 23 test3 = TestBorg2() 24 test4 = TestBorg2()
アウトプット
shell
1$ python special_attr_dict.py 2self._shared_state1: {} 3self._shared_state1: {'state': 'second'} 4self._shared_state2: {} 5self._shared_state2: {}
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答4件
0
投稿2021/05/15 14:52
編集2021/05/15 14:57総合スコア30935
0
まず(__dict__
は完全に忘れて)普通のクラス変数の取り扱いは分かるでしょうか。
python
1class Hoge: 2 shared_list = [] 3 def test_shared(self): 4 print(Hoge.shared_list) 5 Hoge.shared_list.append(1) 6a = Hoge() 7a.test_shared() # => []がprintされて、Hoge.shared_listは[1]になる 8 9b = Hoge() 10b.test_shared() # => [1]がprintされて、Hoge.shared_listは[1, 1]になる 11 12print(Hoge.shared_list) # Hoge.shared_listは[1, 1]なので[1, 1]がprintされる
は分かりますか。
メソッドの中や、クラスの外で、クラス名.属性名
という構文で、クラスが(ここでいうとHoge
が)持っている属性にアクセスする機構のことです。
次に、上のコードと下のコードが(結果として)同じ動きになることが理解できるでしょうか
python
1class Fuga: 2 shared_list = [] 3 def test_shared(self): 4 print(self.shared_list) 5 self.shared_list.append(1) 6a = Fuga() 7a.test_shared() # => []がprintされて、Fuga.shared_listは[1]になる 8 9b = Fuga() 10b.test_shared() # => [1]がprintされて、Fuga.shared_listは[1, 1]になる 11 12print(Fuga.shared_list) # Fuga.shared_listは[1, 1]なので[1, 1]がprintされる 13print(b.shared_list) # shared_listがインスタンス属性から見つからないので、Fuga.shared_listが探索され、[1, 1]なので[1, 1]がprintされる
名前とオブジェクトについて で議論したように、共有データはリストや辞書のような mutable オブジェクトが関与すると驚くべき効果を持ち得ます。
のところのコードの動きと同じことを説明しています。
いろいろな注意点 に
インスタンスとクラスの両方で同じ属性名が使用されている場合、属性検索はインスタンスが優先されます。
とありますが、
「インスタンスとクラスの両方で同じ属性を持っていればインスタンスが優先されます」という説明は、裏から言うと
インスタンス.属性
という形でアクセスする時、インスタンスがその属性を持っていなければクラスの属性を見つけてくるということです。
__dict__
を使った話が理解できるのは、まずこれらが分かってからかと思いますが大丈夫でしょうか。
(追記)
もう一段基本的な方からいきましょう。
Pythonでは、「自分で宣言したクラス」のインスタンスに対して、インスタンス.属性 = 値
という代入文でそのインスタンスになにかの値に属性を束縛できます。
python
1class Foo: 2 pass 3 4a = Foo() 5a.x = 1 6a.y = 2 7 8b = Foo() 9b.y = 3 10 11print(a.x) # => 1がプリントされる 12print(a.y) # => 2がプリントされる 13print(b.x) # => 3がプリントされる 14print(b.y) # => エラー
です。
a.x
とb.x
の値が異なることから、属性はインスタンス毎に付けることが分かります。
これがインスタンス変数です。
(脱線しますが、intやlistのようなPython言語のコアな組み込み型ではこの機構はありません。勝手に属性を追加されたりすると困るので)
クラス定義の外では、クラスに対して、クラス.属性 = 値
という代入文でそのクラスになにかの値に属性を束縛できます。
python
1Foo.c = 1 2 3print(Foo.c) # => 1がプリントされる 4print(Foo.d) # => エラー
"クラス定義の外では"とわざわざ注釈を入れたのは、クラス定義の中では書けないからです。
Python
1class Bar: 2 Bar.c = 1 # => エラー
なぜかというと、Barという名前はclass文の実行が終わった時にできる名前だからです。
実行が終わってないところでは使えません。
どうするかというと
Python
1class Bar: 2 c = 1 3 4print(Bar.c) # => 1
とします。
https://docs.python.org/ja/3/tutorial/classes.html#class-objects
クラスオブジェクトが生成された際にクラスの名前空間にあった名前すべてが有効な属性名です。
クラス宣言の実行部分で名前c
が作られましたから、クラスBarは属性名cを持ちます
ということです。
Python
1class Bar: 2 c = 1
と
python
1class Bar: 2 pass 3Bar.c = 1
は(実行される順番は違うけれども)結果が同じになります。
ここまで分かったら
いろいろな注意点 に
インスタンスとクラスの両方で同じ属性名が使用されている場合、属性検索はインスタンスが優先されます。
とありますが、
「インスタンスとクラスの両方で同じ属性を持っていればインスタンスが優先されます」という説明は、裏から言うと
インスタンス.属性
という形でアクセスする時、インスタンスがその属性を持っていなければクラスの属性を見つけてくる
と書いたのが分かるはずです。
python
1class Bazz: 2 x = 1 3 4a = Bazz() 5a.x = 10 6 7b = Bazz() 8 9print(a.x) # => aに属性xがあって10に束縛されているので10がプリントされる 10print(b.x) # => bに属性xがないので、次にクラスの属性xを探す。1に束縛されているので1がプリントされる
(追記)
もう一段基本的なことを確認しておきたいんですが、
python
1a = {} 2b = a 3 4a['x'] = 1 5 6print(b)
とか
python
1a = [1, 3, 2, 4] 2b = a 3 4a.sort() 5 6print(b)
の結果は、実行せずにすぐ想像できるんでしょうか?
(追記)
では、以上の挙動をすべて理解できていれば、下の結果も実行せずにわかるはずです。
python
1class SharedMutableObject: 2 class_variable = {} 3 4a = SharedMutableObject() 5a.instance_variable = a.class_variable # ポイントはこの代入文の意味 6a.instance_variable["state"] = "first" 7 8print(SharedMutableObject.class_variable) # ここ 9 10 11b = SharedMutableObject() 12b.instance_variable = b.class_variable 13b.instance_variable["state"] = "second" 14 15print(a.class_variable) # ここ 16print(SharedMutableObject.class_variable) # ここ
特に、真ん中のprint(SharedMutableObject.class_variable)
の結果を実行せずに分かるなら、
{'state': 'second'}
がどこで保持され、二回目のインスタンス化であるtest2 = TestBorg1()
の際に{'state': 'second'}
を出力できるのでしょうか?
という疑問は解消されているはずです。(ここに書いたコードでは1回目は'first'を入れていますが)
投稿2021/05/15 14:04
編集2021/05/19 03:57総合スコア11235
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/05/15 23:43
2021/05/16 00:10 編集
2021/05/16 01:50 編集
2021/05/16 05:21 編集
2021/05/16 16:01
2021/05/17 02:35 編集
0
この質問は以下で、順を追って構築していったメカニズムのどこかで、「えっ、なんでこれはこう動くの?」と感じる点があって、そこを尋ねたかったものでしょうか?
それともここに書いてあることに一切不思議な点はない上で何かを尋ねたかったものでしょうか?
(1) グローバル変数に共有オブジェクトを確保して、グローバル変数のオブジェクト間で共有する
python
1g_shared_dict = {} 2 3a = g_shared_dict 4a['state'] = 'first' 5 6b = g_shared_dict 7 8# aであらかじめ更新されていた状態がbから参照できる 9print(b['state']) # => first 10 11# bから更新するとaに及ぶ 12b['state'] = 'second' 13print(a['state']) # => second
(2-a) クラス変数に共有オブジェクトを確保して、グローバル変数のオブジェクト間で共有する
※ (1) のグローバル変数g_shared_dictが、グローバル変数からクラス変数になっただけ
python
1class Foo: 2 _shared_dict = {} 3 4a = Foo._shared_dict 5a['state'] = 'first' 6 7b = Foo._shared_dict 8 9print(b['state']) # => first 10 11# bから更新するとaに及ぶ 12b['state'] = 'second' 13print(a['state']) # => second
(2-b) グローバル変数に共有オブジェクトを確保して、クラスインスタンス間でクラス変数を通じて共有する
※ (1) のグローバル変数a, b が、辞書への参照ではなく Fooの別々のインスタンスになった
※ g_shared_dictへの参照がa, bではなく、aの属性とbの属性になった
python
1g_shared_dict = {} 2 3class Foo: 4 pass 5 6a = Foo() 7a.local_dict_var = g_shared_dict 8a.local_dict_var['state'] = 'first' 9 10b = Foo() 11b.local_dict_var = g_shared_dict 12 13# aであらかじめ更新されていた状態がbから参照できる 14print(b.local_dict_var['state']) # => first 15 16# bから更新するとaに及ぶ 17b.local_dict_var['state'] = 'second' 18print(a.local_dict_var['state']) # => second
(3) クラス変数に共有オブジェクトを確保して、クラスインスタンス間でクラス変数を通じて共有する
※ (2-a) (2-b) の混交
python
1class Foo: 2 _shared_dict = {} 3 4a = Foo() 5a.local_dict_var = a._shared_dict # ★ 6a.local_dict_var['state'] = 'first' 7 8b = Foo() 9b.local_dict_var = b._shared_dict # ★ 10 11# aであらかじめ更新されていた状態がbから参照できる 12print(b.local_dict_var['state']) # => first 13 14# bから更新するとaに及ぶ 15b.local_dict_var['state'] = 'second' 16print(a.local_dict_var['state']) # => second
(4) 共有オブジェクトへの参照を初期化子に移す
※ (3)の★のコードが__init___
に移動しただけ
python
1class Foo: 2 _shared_dict = {} 3 4 def __init__(self): 5 self.local_dict_var = self._shared_dict 6 7a = Foo() 8a.local_dict_var['state'] = 'first' # ☆ 9 10b = Foo() 11 12# aであらかじめ更新されていた状態がbから参照できる 13print(b.local_dict_var['state']) # => first 14 15# bから更新するとaに及ぶ 16b.local_dict_var['state'] = 'second' 17print(a.local_dict_var['state']) # => second
(5) 共有オブジェクトへの代入を初期化子に移す
※ (4)の☆のコードが__init___
に移動しただけ
python
1class Foo: 2 _shared_dict = {} 3 4 def __init__(self): 5 self.local_dict_var = self._shared_dict 6 self.local_dict_var['state'] = 'first' 7 8a = Foo() 9 10b = Foo() 11 12# bから更新するとaに及ぶ 13b.local_dict_var['state'] = 'second' 14print(a.local_dict_var['state']) # => second
(6) インスタンス変数名を__dict__
に
※ (5)のlocal_dict_var
の名前を変えただけ
python
1class Foo: 2 _shared_dict = {} 3 4 def __init__(self): 5 self.__dict__ = self._shared_dict 6 self.__dict__['state'] = 'first' 7 8a = Foo() 9 10b = Foo() 11 12# bから更新するとaに及ぶ 13b.__dict__['state'] = 'second' 14print(a.__dict__['state']) # => second
投稿2021/05/28 23:18
総合スコア11235
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
Pythonの組み込み関数idと同一性の比較isを使って考えてください。
これでご質問の疑問は解消するでしょうか。
ご存じない場合は先に以下をお読み下さい。
組み込み関数id
同一性の比較is
以下のコード実行してみると、何が同じで何が同じでないかが分かります。
python
1class TestBorg1: 2 _shared_state = {} 3 def __init__(self): 4 print(f"self._shared_state1: {self._shared_state}") 5 self.__dict__ = self._shared_state 6 self.__dict__["state"] = "second" 7 8class TestBorg2: 9 _shared_state = {} 10 def __init__(self): 11 print(f"self._shared_state2: {self._shared_state}") 12 self.__dict__["val"] = self._shared_state 13 self.__dict__["val"] = "test" 14 15test1 = TestBorg1() 16test2 = TestBorg1() 17test3 = TestBorg2() 18test4 = TestBorg2() 19 20print(test1.__dict__ is test2.__dict__) 21print(test3.__dict__ is test4.__dict__) 22 23print(id(test1.__dict__)) 24print(id(test2.__dict__)) 25print(id(test3.__dict__)) 26print(id(test4.__dict__))
最後の部分の実行結果です。
python
1>>> print(test1.__dict__ is test2.__dict__) 2True 3>>> print(test3.__dict__ is test4.__dict__) 4False
explanation
1つまり、test1.__dict__ と test2.__dict__は同じオブジェクトですが、 2test3.__dict__ と test4.__dict__は同じオブジェクトではありません。 3それは演算子isの定義であるidを表示した以下の結果から当然です。
python
1>>> print(id(test1.__dict__)) 22726063276352 3>>> print(id(test2.__dict__)) 42726063276352 5>>> print(id(test3.__dict__)) 62726064619392 7>>> print(id(test4.__dict__)) 82726064638656
投稿2021/05/15 19:14
総合スコア24670
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/05/15 20:09
2021/05/15 20:10
2021/05/16 06:29
2021/05/16 06:37 編集
2021/05/16 15:58
2021/05/17 13:02 編集
2021/05/17 13:13
2021/05/17 13:18
2021/05/19 04:27
2021/05/19 05:40 編集
2021/05/20 15:04 編集
2021/05/19 05:50 編集