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

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

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

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

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

Python

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

Q&A

1回答

598閲覧

Borgパターンのクラス変数の状態共有のしくみ

sequelanonymous

総合スコア123

Python 3.x

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

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

Python

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

0グッド

0クリップ

投稿2021/04/30 07:28

編集2021/04/30 07:28

デザインパターンの勉強をしています。
下記のコードで二回目のIBorg()のインスタンスを作成した際に、すでにself._shared_state{'state': 'init'}がなぜ格納されているのでしょうか?

python

1class Borg: 2 _shared_state = {} 3 def __init__(self): 4 print(f"self._shared_state: {self._shared_state}") 5 self.__dict__ = self._shared_state 6 7class IBorg(Borg): 8 def __init__(self): 9 Borg.__init__(self) 10 self.state = 'init' 11 12 def __str__(self): 13 return self.state 14 15if __name__ == "__main__": 16 i1 = IBorg() 17 print(f"i1: {i1}") 18 i2 = IBorg() 19 print(f"i2: {i2}")

アウトプット

python

1$ python xxx.py 2self._shared_state: {} 3i1: init 4self._shared_state: {'state': 'init'} 5i2: init

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

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

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

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

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

guest

回答1

0

answer

1i1.__dict__とi2.__dict__が同じだからです。 2 3試しに 4print(i1.__dict__ is i2.__dict__)を実行してみてください。

投稿2021/04/30 07:35

ppaul

総合スコア24666

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

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

sequelanonymous

2021/04/30 07:42

それは同じですね。self._shared_stateになぜ、いつ、どうやって{'state': 'init'}が格納されてるのでしょうか?
ppaul

2021/04/30 07:45

i1を生成したときです。
sequelanonymous

2021/04/30 07:51 編集

そうはおもえないのですが、i1 = IBorg()の行でself._shared_stateに{'state': 'init'}が格納されてる、とおっしゃっていますでしょうか?
ppaul

2021/04/30 08:37

以下の実行結果を見ればわかるように、i1とi2は__dict__を共有しているので属性も共有します。 >>> i1 = IBorg() self._shared_state: {} >>> i2 = IBorg() self._shared_state: {'state': 'init'} >>> >>> i1.aaa = 100 >>> print(i2.aaa) 100
sequelanonymous

2021/04/30 11:15

共有している点はわかっています。i1 = IBorg()の行で、なぜ、{'state': 'init'}が格納されてるのかがわかりません。_self_sharedに辞書でデータをいれる記述は書いてないはずなのですが。最初に空dictをいれただけであって。
ppaul

2021/04/30 16:19

属性が__dict__で管理されているというのは理解していますか? __dict__を共有しているということは、i1の属性を設定すると、それはi2の属性としても設定されるということですね。 i1を生成したときに、 self.state = 'init' が実行されたといいうことは、i1.__dict__['state']='init'を実行したということです。 i1.__dict__は、Borg._shared_stateなのですから、Borg._shared_state['state']='init'を実行したといっても同じです。 従って、i2が生成されたときに動くBorg.__init__(self)の中で、 print(f"self._shared_state: {self._shared_state}") が実行されたときには self._shared_state: {'state': 'init'} が印字されます。
sequelanonymous

2021/04/30 17:18

> 属性が__dict__で管理されているというのは理解していますか? もしかしたら私の理解が間違ってる可能性がありますが、一つ一つ確認していきたいので補足していただけると助かります。 > __dict__を共有しているということは、i1の属性を設定すると、それはi2の属性としても設定されるということですね。 ここは、コードを読んだとおりにそうなるなと理解できます。 > i1を生成したときに、self.state = 'init'が実行されたといいうことは、i1.__dict__['state']='init'を実行したということです。 ここも、なんとかまーそうだろうなーと理解できます。 > i1.__dict__は、Borg._shared_stateなのですから、Borg._shared_state['state']='init'を実行したといっても同じです。 ここが理解できません。 i1.__dict__は、{}だと思っています。なぜ、Borg._shared_state['state']='init'を実行したことになるのか理解ができません。'init'を格納してる先は、コードをみるかぎり、self.stateだとおもっています。 > 従って、i2が生成されたときに動くBorg.__init__(self)の中で、print(f"self._shared_state:{self._shared_state}")が実行されたときにはself._shared_state: {'state': 'init'}が印字されます。 ここは、コードを読んだ通りで理解できます。
quickquip

2021/05/01 01:24 編集

肝心の > 属性が__dict__で管理されているというのは理解していますか? に答えないのはなんででしょう? (核心部分を聞かれたのに、話をめちゃくちゃそらしているのが気になったのでつい)
actorbug

2021/05/01 20:32 編集

もしかして、このあたりが理解できていない? なぜ list 'y' を変更すると list 'x' も変更されるのですか? https://docs.python.org/ja/3/faq/programming.html#why-did-changing-list-y-also-change-list-x list 'y' を i1.__dict__ に、list 'x' を Borg._shared_state に置き換えれば、同じ構図になります。 # Borg の定義内の「_shared_state = {}」 Borg._shared_state = {} # Borg.__init__ 内の「self.__dict__ = self._shared_state」 i1.__dict__ = Borg._shard_state # IBorg.__init__ 内の「self.state = 'init'」 i1.__dict__['state'] = 'init'
hayataka2049

2021/05/01 09:02 編集

そもそも__init__で親クラスの__init__を呼んでいるところを追いきれてないのかもしれません。 printを取っ払って展開したら、 class IBorg(Borg): def __init__(self): self.__dict__ = Borg._shared_state self.state = 'init' になる訳なんですが、まずそれが大丈夫なのか問題。 # あと、self経由クラス変数アクセスの理解という問題もありますね。上のコードでは直接クラス属性として書きましたが。
sequelanonymous

2021/05/02 04:47

ありがとうございます! > なぜ list 'y' を変更すると list 'x' も変更されるのですか? この辺りの理解が足りなてなかったように思います。 一点ご確認させていただきたいのですが、わざわざ`self.__dict__`に空dictをいれる理由ってなんでしょうか? 下記のような書き方でもいいのかなとおもいました。 ``` class Borg: _shared_state = {} def __init__(self): print(f"self._shared_state: {self._shared_state}") self.state = self._shared_state class IBorg(Borg): def __init__(self): self.state = 'init' ```
ppaul

2021/05/02 12:39

わざわざ`self.__dict__`に空dictをいれる理由ってなんでしょうか? の答えは、 Borg._shared_stateという、作られて時には空だったが、__init__が実行されるときには体はないかもしれない辞書であり、誰かがその辞書に追加したらさらに中身が増えるかもしれない辞書を設定しているからです。 つまり「an empty dictionary(とある空の辞書)」ではなく、「the dictionary(その特定の辞書)」を代入しているのです。 従って、self.state = self._shared_stateでは全く違う動きになります。
actorbug

2021/05/03 22:34

わざわざ self.__dict__ に「共通の」dictをいれる理由は、Borg の1つのインスタンスへの変更を、即座にすべての Borg のインスタンスに反映するためです。 (self.__dict__ = {}のように単なる空dictを入れても効果がないので、「共通の」に変更しました) 例えば、元のコードで i1、i2 を作成した後で i1.state = 'next' のように属性値を書き換えると、i2.state も同じ値に即座に書き換わります。 さらに、i1.x = 2 のように新たな属性を後から追加した場合でも、i2 に同じ属性が即座に追加されます。 コメントのコードの場合、そのような動作にはなりません。
sequelanonymous

2021/05/07 12:05 編集

ありがとうございます! > つまり「an empty dictionary(とある空の辞書)」ではなく、「the dictionary(その特定の辞書)」を代入しているのです。 すみません、”とある”と”その特定の”の意味がわからないのでもう少し具体的に説明していただけると助かります。 > 例えば、元のコードで i1、i2 を作成した後で i1.state = 'next' のように属性値を書き換えると、i2.state も同じ値に即座に書き換わります。 はい、変更できるのは、それはコードに書いてあるとおりそうなのでわかります。 あれこれ実装してみて確認してみましたが、なぜ、self.__dict__ = self._shared_stateを追記するだけで違うクラスインスタンスを作成しても、異なるインスタンスでも__dict__のオブジェクトIDが同じままなのか、がわかりません。 例えば、下記だと__dict__のIDは同じです。がゆえに共有できるのだと思います。 ```python class TestBorg: _shared_state = {} def __init__(self): self.__dict__ = self._shared_state self.state = "test_init" print(id(self.__dict__)) test1 = TestBorg() test2 = TestBorg() -> 4482057856 -> 4482057856 ``` 一方で、下記のコードでは__dict__のIDは異なります。が故に共有ができません。その理由が知りたいです。 ```python class TestBorg: _shared_state = {} def __init__(self): self.val = self._shared_state self.val = "aaa" print(id(self.__dict__)) test1 = TestBorg() test2 = TestBorg() -> 4478128640 -> 4479011776 ```
actorbug

2021/05/06 12:39 編集

まず、いったんクラスのことは忘れて、 _shared_state = {} i1 = {} i1 = _shared_state i1['state'] = 'init' print(id(i1)) i2 = {} i2 = _shared_state i2['state'] = 'init' print(id(i2)) が同じIDになって、 _shared_state = {} i1 = {} i1['state'] = _shared_state i1['state'] = 'init' print(id(i1)) i2 = {} i2['state'] = _shared_state i2['state'] = 'init' print(id(i2)) が違うIDになるのは理解できるの? 理解できるなら、i1,i2をi1.__dict__,i2.__dict__に置き換えれば、クラスでの動作も理解できるはず。  (i1.__dict__ = {}は、pythonがオブジェクト生成時に自動で実行するものと解釈すればいい) 理解できないなら、各行実行ごとに各変数のIDがどうなるか表示してみればわかるかも。 (i1['state']=... を実行した後はi1['state']のIDも) それでも理解できないなら、私じゃ無理なので、ネット上にある変数代入の解説を検索して読んでくれ。
hayataka2049

2021/05/06 13:29

id(self._shared_state) や、 id(Borg._shared_state) や、 x=self._shared_stateしたあとのid(x) なんかも見てみると良いのかもしれませんね。
ppaul

2021/05/06 15:30

self.__dict__ = self._shared_stateを追記するだけで違うクラスインスタンスを作成しても、異なるインスタンスでも__dict__のオブジェクトIDが同じままなのか、がわかりません。 これの回答は、追記した場所が__init__の中だからです。 https://docs.python.org/ja/3/reference/datamodel.html?highlight=__init__#object.__init__ を読んでください。 下記のコードでは__dict__のIDは異なります。が故に共有ができません。その理由が知りたいです。 という質問が、なぜ個々のインスタンスオブジェクトで__dict__のIDが異なるのか、という意味なら、回答は以下です。 公式ドキュメントには書かれていませんが、静的メソッド__new__は、空のdictオブジェクトを生成して、特殊属性__dict__に設定します。このため、__new__を定義してそこで__dict__を変更するか、__init__で__dict__を変更しない限り、そのクラスのインスタンスオブジェクトの__dict__は全て異なったdictオブジェクトになります。
sequelanonymous

2021/05/07 14:20 編集

ご回答ありがとうございます! > これの回答は、追記した場所が__init__の中だからです。 > 静的メソッド__new__は、空のdictオブジェクトを生成して、特殊属性__dict__に設定します。このため、__new__を定義してそこで__dict__を変更するか、__init__で__dict__を変更しない限り、そのクラスのインスタンスオブジェクトの__dict__は全て異なったdictオブジェクトになります。 まさに確認したい箇所は、上記の部分でしたが、もう少し踏み込んだ理解をしたいと思っていて、上記のIDが異なることを確認した際には、下記のような仮説をもっていました。 1) `self.__dict__`に保存する場合と`self.val`にオブジェクトを保存するのとでは、確保しているメモリの記憶領域がことなる。 2) `def __init__` or `def __new__` で `self.__dict__`に保存する場合、一度クラスオブジェクトをインスタンス化すると、`__dict__`に保存され、2回目以降のインスタンス化では同じ`__dict__`オブジェクトが利用される。 3) `__dict__`に保存されるのは、インメモリでメモリ領域に空きがなくなるまで保存される。空きがなくなったらCPythonのガベージコレクションがよしなにメモリ解放する。 この1)と2)と3)の仮説は、多から少なかれ大枠あっていますでしょうか?間違ってる、もしくはこの部分は関係がない、などあればご指摘いただけると助かります。 参照URL:: https://docs.python.org/ja/3.5/reference/datamodel.html https://stackify.com/python-garbage-collection/
ppaul

2021/05/10 05:20

1) `self.__dict__`に保存する場合と`self.val`にオブジェクトを保存するのとでは、確保しているメモリの記憶領域がことなる。 確保しているメモリの記憶領域が何を指しているかによります。 オブジェクト自体ですか、それともオブジェクトを覚えておくための領域ですか。 2) `def __init__` or `def __new__` で `self.__dict__`に保存する場合、一度クラスオブジェクトをインスタンス化すると、`__dict__`に保存され、2回目以降のインスタンス化では同じ`__dict__`オブジェクトが利用される。 間違いです。これをやると、全てのインスタンスの属性値が同じになってしまいます。質問にあったコードは、ここを変更して、全てのインスタンスの属性値を同じにするためのものです。 3) `__dict__`に保存されるのは、インメモリでメモリ領域に空きがなくなるまで保存される。空きがなくなったらCPythonのガベージコレクションがよしなにメモリ解放する。 全てのオブジェクトはインメモリで存在します。 Pythonのガベージコレクションは参照カウンタ方式ですので、必要かどうかと関係なく参照のなくなったメモリを解放します。ただし、解放されたメモリのつなぎあわせは行っていないので、メモリの総量としては空きがあるように見えても、大きな一塊のメモリをとれないことは良くあります。その場合に、Pythonはエラーを出して終了します。
sequelanonymous

2021/05/10 20:31

ありがとうございます! > 静的メソッド__new__は、空のdictオブジェクトを生成して、特殊属性__dict__に設定します。このため、__new__を定義してそこで__dict__を変更するか、__init__で__dict__を変更しない限り、そのクラスのインスタンスオブジェクトの__dict__は全て異なったdictオブジェクトになります。 > 質問にあったコードは、ここを変更して、全てのインスタンスの属性値を同じにするためのものです。 上記についてもう少し確認したいです。 一回目のインスタンス化のさいに__dict__の変更は、どこで保存?!維持され、2回目のインスタンス化のさいでも同じ__dict__を利用しているのでしょうか? また、一回目のインスタンス化のとき、 self.val = self._shared_state のself.valは、{"val":{}}っていう理解であっていますか? 一方、self.__dict__ = self._shared_state は、self.__dict__は、ただの{}になる。で、共通のこのdictを使って中身を更新していっている、で理解あっていますか?
hayataka2049

2021/05/10 23:22 編集

>__dict__の変更は、どこで保存?!維持され 厳密にはここを見ろってことになるのでしょうが…… https://docs.python.org/ja/3/c-api/typeobj.html#tp-slots Pythonの(一部組み込み型を除く)オブジェクトは、属性管理用辞書への参照を持ちます。Pythonからそれへのアクセスは__dict__という属性名で可能になっています。 (ややこしいけど、__dict__は唯一の属性管理の仕組みではないのです) オブジェクトの領域内に__dict__がある訳ではないので、念の為ご注意を。参照なので自由自在に張り替えられる訳です。 ……いや、そもそもPythonはオブジェクトへの参照しかない言語なのですが。値としてアクセスすることも可能という勘違いがありそうな気がする。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問