インスタンス間でクラスデータを共有しないようにするにはどうすればよいですか?


145

私が欲しいのはこの動作です:

class a:
    list = []

x = a()
y = a()

x.list.append(1)
y.list.append(2)
x.list.append(3)
y.list.append(4)

print(x.list) # prints [1, 3]
print(y.list) # prints [2, 4]

もちろん、印刷すると実際に何が起こりますか。

print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]

明らかに彼らはクラスでデータを共有していますa。希望する動作を実現するために個別のインスタンスを取得するにはどうすればよいですか?


14
list属性名として使用しないでください。list新しいリストを作成するための組み込み関数です。名前のクラスは大文字で書く必要があります。
MarcTudurí、2015年

回答:


147

あなたはこれを求めている:

class a:
    def __init__(self):
        self.list = []

クラス宣言内で変数を宣言すると、変数はインスタンスメンバーではなく「クラス」メンバーになります。__init__メソッド内でそれらを宣言すると、メンバーの新しいインスタンスがオブジェクトのすべての新しいインスタンスと一緒に作成されます。これは、探している動作です。


5
追加の説明:インスタンスの1つでリストプロパティを再割り当てしても、他のインスタンスには影響しません。したがって、のようなことをした場合x.list = []、それを変更して他の人に影響を与えないようにすることができます。あなたが直面している問題はそれx.listy.list同じリストであるため、片方で追加を呼び出すと、もう片方に影響します。
Matt Moriarity、2009年

しかし、なぜこれがリストでのみ発生するのですか?initの外で整数または文字列を宣言したとき、オブジェクト間で共有されませんでしたか?誰でもこのコンセプトへのドキュメントリンクを共有できますか?
Amal Ts、

1
@AmalTs Pythonでの割り当てがどのように機能するか理解していないようです。このビデオまたはこのSO投稿を参照してください。表示される動作は、リストを変更しているが、参照をintおよびstringに再バインドしていることが原因です。
АндрейБеньковский

4
@AmalTs注:クラス属性をインスタンス属性の「遅延」デフォルト値として使用することは悪い習慣と見なされています。属性が不変のタイプであっても、内で割り当てることをお勧めします__init__
АндрейБеньковский

21

受け入れられた回答は機能しますが、もう少し説明しても問題はありません。

クラス属性は、インスタンスの作成時にインスタンス属性にはなりません。値が割り当てられると、インスタンス属性になります。

元のコードではlist、インスタンス化後に属性に値が割り当てられていません。そのため、クラス属性のままです。インスタンス化後に呼び出される__init__ため、内部でリストを定義する__init__と機能します。または、このコードは、必要な出力も生成します。

>>> class a:
    list = []

>>> y = a()
>>> x = a()
>>> x.list = []
>>> y.list = []
>>> x.list.append(1)
>>> y.list.append(2)
>>> x.list.append(3)
>>> y.list.append(4)
>>> print(x.list)
[1, 3]
>>> print(y.list)
[2, 4]

ただし、数値や文字列などの不変オブジェクトには、割り当てなしでは値を変更できないため、問題の混乱するシナリオは発生しません。たとえば、元の文字列属性タイプと同様のコードは問題なく機能します。

>>> class a:
    string = ''


>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'

つまり、クラス属性は、インスタンス化された後、__init__メソッド内にあるかどうかにかかわらず、値がそれらに割り当てられている場合に限り、インスタンス属性になります。これにより、インスタンス化後に属性に値を割り当てない場合に静的属性を設定できるため、これは良いことです。


11

「リスト」を「インスタンスレベルのプロパティ」ではなく「クラスレベルのプロパティ」として宣言しました。プロパティをインスタンスレベルでスコープ指定するには、__init__メソッド内の(または状況に応じて他の場所で)"self"パラメータを参照してプロパティを初期化する必要があります。

__init__メソッドのインスタンスプロパティを厳密に初期化する必要はありませんが、理解しやすくなります。


11

受け入れられた回答はその場にありますが、少し説明を追加したいと思います。

ちょっと練習しましょう

まず、次のようにクラスを定義します。

class A:
    temp = 'Skyharbor'

    def __init__(self, x):
        self.x = x

    def change(self, y):
        self.temp = y

では、ここには何がありますか?

  • temp文字列である属性を持つ非常に単純なクラスがあります
  • __init__設定する方法self.x
  • 変更方法セット self.temp

これまでのところ、かなり簡単です。では、このクラスをいじってみましょう。最初にこのクラスを初期化しましょう:

a = A('Tesseract')

次のようにします。

>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor

まあ、a.temp期待どおりに動作しましたが、地獄はどのようにA.temp機能しましたか?tempはクラス属性なので、うまくいきました。Pythonのすべてがオブジェクトです。ここでAもclassのオブジェクトですtype。したがって、属性tempはAクラスが保持する属性であり、tempの値をA(のインスタンスではなくa)変更すると、変更された値はAクラスのすべてのインスタンスに反映されます。先に進んでそれをしましょう:

>>> A.temp = 'Monuments'
>>> print(A.temp)
Monuments
>>> print(a.temp)
Monuments

面白いですね。そしてまだ同じであることに注意してくださいid(a.temp)id(A.temp)

すべてのPythonオブジェクトには、__dict__属性のリストが含まれる属性が自動的に与えられます。このディクショナリに含まれるオブジェクトの例を調べてみましょう。

>>> print(A.__dict__)
{
    'change': <function change at 0x7f5e26fee6e0>,
    '__module__': '__main__',
    '__init__': <function __init__ at 0x7f5e26fee668>,
    'temp': 'Monuments',
    '__doc__': None
}
>>> print(a.__dict__)
{x: 'Tesseract'}

temp属性は、インスタンスに対してリストされている間、Aクラスの属性の中にリストされていることに注意してくださいx

ではa.temp、インスタンスにリストされていない場合でも、定義された値を取得するのはなぜですかa。まあそれは__getattribute__()メソッドの魔法です。Pythonでは、ドット構文により自動的にこのメソッドが呼び出されるためa.temp、Pythonを実行すると、Pythonが実行されa.__getattribute__('temp')ます。このメソッドは、属性ルックアップアクションを実行します。つまり、さまざまな場所を調べて属性の値を見つけます。

の標準実装は、__getattribute__()最初にオブジェクトの内部辞書(dict)を検索し、次にオブジェクト自体のタイプを検索します。この場合、a.__getattribute__('temp')最初に実行されa.__dict__['temp']、次にa.__class__.__dict__['temp']

では、changeメソッドを使用しましょう。

>>> a.change('Intervals')
>>> print(a.temp)
Intervals
>>> print(A.temp)
Monuments

さてself、使用したので、とprint(a.temp)は異なる値が得られprint(A.temp)ます。

では、とを比較するid(a.temp)id(A.temp)、それらは異なります。


3

はい。リストをクラスプロパティではなくオブジェクトプロパティにする場合は、「コンストラクタ」で宣言する必要があります。


3

したがって、ここでのほぼすべての応答は、特定の点を見逃しているようです。以下のコードで示されているように、クラス変数インスタンス変数になりません。メタクラスを利用してクラスレベルで変数の割り当てをインターセプトすると、a.myattrが再割り当てされたときに、クラスのフィールド割り当てのマジックメソッドが呼び出されないことがわかります。これは、割り当てによって新しいインスタンス変数が作成されるためです。この動作は、クラス変数がなくてもフィールド割り当てが可能な2番目のクラスで示されているように、クラス変数とはまったく関係ありません。

class mymeta(type):
    def __init__(cls, name, bases, d):
        pass

    def __setattr__(cls, attr, value):
        print("setting " + attr)
        super(mymeta, cls).__setattr__(attr, value)

class myclass(object):
    __metaclass__ = mymeta
    myattr = []

a = myclass()
a.myattr = []           #NOTHING IS PRINTED
myclass.myattr = [5]    #change is printed here
b = myclass()
print(b.myattr)         #pass through lookup on the base class

class expando(object):
    pass

a = expando()
a.random = 5            #no class variable required
print(a.random)         #but it still works

IN SHORTクラス変数は、インスタンス変数とは何の関係もありません。

より明確に、それらはたまたまインスタンスのルックアップのスコープ内にあります。クラス変数は、実際にはクラスオブジェクト自体のインスタンス変数です。メタクラス自体もオブジェクトなので、必要に応じてメタクラス変数を使用することもできます。他のオブジェクトの作成に使用されるかどうかに関係なく、すべてがオブジェクトであるため、他の言語の単語クラスの使用法の意味に縛られないでください。Pythonでは、クラスは実際には単なるオブジェクトであり、他のオブジェクトの作成方法とその動作を決定するために使用されます。メタクラスは、この点をさらに説明するために、クラスを作成するクラスです。


0

他のインスタンスによって共有されている変数を保護するには、インスタンスを作成するたびに新しいインスタンス変数を作成する必要があります。クラス内で変数を宣言する場合、それはクラス変数であり、すべてのインスタンスで共有されます。インスタンスにしたい場合は、インスタンスを参照して変数を再初期化するためにinitメソッドを使用する必要があります

Programiz.comによるPythonオブジェクトとクラスから:

__init__()関数。この特別な関数は、そのクラスの新しいオブジェクトがインスタンス化されるたびに呼び出されます。

このタイプの関数は、オブジェクト指向プログラミング(OOP)ではコンストラクターとも呼ばれます。通常は、すべての変数を初期化するために使用します。

例えば:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.