web-dev-qa-db-ja.com

Pythonクラスメンバーの初期化

私は最近Pythonのバグと戦いました。それはそれらの愚かな初心者のバグの1つでしたが、Python(私はPythonに慣れていないC++プログラマーです)のメカニズムについて考えるようになりました。バグのあるコードをレイアウトしますそして、私がそれを修正するために何をしたかを説明してから、いくつか質問があります...

シナリオ:Aという名前のクラスがあり、そのクラスには辞書データメンバーがあり、そのコードは次のとおりです(これはもちろん単純化です)。

_class A:
    dict1={}

    def add_stuff_to_1(self, k, v):
        self.dict1[k]=v

    def print_stuff(self):
        print(self.dict1)
_

このコードを使用するクラスはクラスBです。

_class B:

    def do_something_with_a1(self):
        a_instance = A()
        a_instance.print_stuff()        
        a_instance.add_stuff_to_1('a', 1)
        a_instance.add_stuff_to_1('b', 2)    
        a_instance.print_stuff()

    def do_something_with_a2(self):
        a_instance = A()    
        a_instance.print_stuff()            
        a_instance.add_stuff_to_1('c', 1)
        a_instance.add_stuff_to_1('d', 2)    
        a_instance.print_stuff()

    def do_something_with_a3(self):
        a_instance = A()    
        a_instance.print_stuff()            
        a_instance.add_stuff_to_1('e', 1)
        a_instance.add_stuff_to_1('f', 2)    
        a_instance.print_stuff()

    def __init__(self):
        self.do_something_with_a1()
        print("---")
        self.do_something_with_a2()
        print("---")
        self.do_something_with_a3()
_

do_something_with_aX()を呼び出すたびに、クラスAの新しい「クリーン」インスタンスが初期化され、追加の前後に辞書が出力されることに注意してください。

バグ(まだ理解していない場合):

_>>> b_instance = B()
{}
{'a': 1, 'b': 2}
---
{'a': 1, 'b': 2}
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
---
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
{'a': 1, 'c': 1, 'b': 2, 'e': 1, 'd': 2, 'f': 2}
_

クラスAの2回目の初期化では、辞書は空ではありませんが、最後の初期化の内容から始まります。私は彼らが「新鮮」に始めることを期待していました。

この「バグ」を解決するのは明らかに次を追加することです。

_self.dict1 = {}
_

クラスAの___init___コンストラクターで。

  1. Dict1の宣言ポイント(クラスAの最初の行)での「dict1 = {}」初期化の意味は何ですか?無意味ですか?
  2. 最後の初期化から参照をコピーするインスタンス化のメカニズムは何ですか?
  3. 「self.dict1 = {}」をコンストラクター(またはその他のデータメンバー)に追加すると、以前に初期化されたインスタンスのディクショナリーメンバーにどのような影響がありませんか?

編集:回答に続いて、データメンバーを宣言し、___init___または他の場所でself.dict1として参照しないことで、C++/Javaで静的データメンバーと呼ばれるものを実際に定義していることを理解しています。 self.dict1と呼ぶことで、「インスタンスにバインド」しています。

41
Roee Adler

バグとして言及し続けるのは、 文書化された 、Pythonクラスの標準的な動作です。

最初に行ったように__init__の外でdictを宣言することは、クラスレベルの変数を宣言することです。新しいオブジェクトを作成するたびに、この同じ辞書を再利用します。インスタンス変数を作成するには、__init__selfで宣言します。それと同じくらい簡単です。

52

@Matthew:オブジェクト指向プログラミングのクラスメンバーとオブジェクトメンバーの違いを確認してください。この問題は、元の辞書の宣言がオブジェクトメンバーではなくクラスメンバーになるために発生します(元の投稿者の意図どおり)。したがって、クラスのすべてのインスタンスに対して1回(共有)存在します(つまり1回)クラス自体の場合は、クラスオブジェクト自体のメンバーとして)、動作は完全に正しいです。

2
W.Prins

インスタンスの属性、たとえばself.fooにアクセスすると、pythonは最初にself.__dict__で 'foo'を見つけます。見つからない場合は、python TheClass.__dict__で 'foo'が見つかります

あなたの場合、dict1はインスタンスではなくクラスAです。

2
kcwu

これがあなたのコードの場合:

class ClassA:
    dict1 = {}
a = ClassA()

次に、おそらくこれはPython内で起こると予想していました。

class ClassA:
    __defaults__['dict1'] = {}

a = instance(ClassA)
# a bit of pseudo-code here:
for name, value in ClassA.__defaults__:
    a.<name> = value

私が知る限り、isは何が起こるかです。ただし、dictの値の代わりにポインターがコピーされます。これはPythonのどこでもデフォルトの動作です。このコードを見てください:

a = {}
b = a
a['foo'] = 'bar'
print b
0
too much php

Pythonクラス宣言はコードブロックとして実行され、ローカル変数定義(その関数定義は特別な種類です)は構築されたクラスインスタンスに格納されます。 Pythonでの属性検索の仕組みにより、インスタンスで属性が見つからない場合、クラスの値が使用されます。

クラス構文に関する興味深い記事 Python blog。

0
Ants Aasma