クラスとインスタンスの属性の違いは何ですか?


132

次の間に意味のある違いはありますか?

class A(object):
    foo = 5   # some default value

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

多数のインスタンスを作成している場合、2つのスタイルのパフォーマンスまたはスペース要件に違いはありますか?コードを読むとき、2つのスタイルの意味は大きく異なると思いますか?


1
同様の質問がここで行われ、答えられることに気づきました:stackoverflow.com/questions/206734/… この質問を削除する必要がありますか?
Dan Homerick 2008年

2
それはあなたの質問です、それを削除してください。それはあなたのものなので、なぜ他の誰かの意見を聞くのですか?
S.Lott、2008年

回答:


146

パフォーマンスに関する考慮事項以外にも、意味上の大きな違いがあります。クラス属性の場合、参照されるオブジェクトは1つだけです。instance-attribute-set-at-instantiationでは、複数のオブジェクトを参照できます。例えば

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]

4
変更可能なタイプのみが共有されます。のようにintstr彼らはまだクラスではなく各インスタンスに接続されています。
Babu 2014

11
@Babu:いいえ、intとはstrされても、全く同じ方法で、共有しました。isまたはで簡単に確認できidます。または、各インスタンス__dict__とクラスのを調べます__dict__。不変の型が共有されているかどうかは、通常はそれほど重要ではありません。
abarnert 2014年

17
ただし、これを行うa.foo = 5と、どちらの場合でもb.fooreturn が表示されます[]。これは、最初のケースでa.fooは、同じ名前の新しいインスタンス属性でクラス属性を上書きしているためです。
Konstantin Schubert、

39

違いは、クラスの属性がすべてのインスタンスで共有されることです。インスタンスの属性は、そのインスタンスに固有です。

C ++からの場合、クラスの属性は静的メンバー変数に似ています。


1
共有されるのは可変タイプだけではないですか?受け入れ答えショー動作しますが、それはint型であれば、インスタンスattrのと同じであるように思わリスト、: >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5
レイフ

6
@Rafe:いいえ、すべてのタイプが共有されます。あなたが混乱している理由は何がということですa.foo.append(5)値変異されa.fooている間、を参照しa.foo = 5つつあるa.foo値の新しい名前に5。つまり、クラス属性を非表示にするインスタンス属性になります。a.foo = 5Alexのバージョンでも同じことを試してみてくださいb.foo。変更されていないことがわかります。
abarnert 2014年

30

ここに非常に良い投稿があります、そして以下のように要約してください。

class Bar(object):
    ## No need for dot syntax
    class_var = 1

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

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = MyClass(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

そして視覚的な形で

ここに画像の説明を入力してください

クラス属性の割り当て

  • クラスにアクセスしてクラス属性を設定すると、すべてのインスタンスの値がオーバーライドされます

    foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2
  • インスタンスにアクセスしてクラス変数を設定すると、そのインスタンスののみがオーバーライドされます。これは基本的にクラス変数をオーバーライドし、直感的に、そのインスタンスのみ使用可能なインスタンス変数に変換します

    foo = Bar(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1

いつクラス属性を使用しますか?

  • 定数の保存。クラス属性はクラス自体の属性としてアクセスできるため、クラス全体のクラス固有の定数を格納するためにそれらを使用すると便利な場合があります

    class Circle(object):
         pi = 3.14159
    
         def __init__(self, radius):
              self.radius = radius   
        def area(self):
             return Circle.pi * self.radius * self.radius
    
    Circle.pi
    ## 3.14159
    c = Circle(10)
    c.pi
    ## 3.14159
    c.area()
    ## 314.159
  • デフォルト値の定義。簡単な例として、制限付きリスト(つまり、特定の数以下の要素しか保持できないリスト)を作成し、10アイテムのデフォルトの上限を選択することができます。

    class MyClass(object):
        limit = 10
    
        def __init__(self):
            self.data = []
        def item(self, i):
            return self.data[i]
    
        def add(self, e):
            if len(self.data) >= self.limit:
                raise Exception("Too many elements")
            self.data.append(e)
    
     MyClass.limit
     ## 10

19

ここのコメントと、重複としてマークされた他の2つの質問の人々はすべて、同じようにこれについて混乱しているように見えるので、Alex Coventryの上にさらに回答を追加する価値があると思います。

Alexがリストのように変更可能な型の値を割り当てるという事実は、物事が共有されているかどうかには関係ありません。これはid関数またはis演算子で確認できます。

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

object()たとえば、の代わりに私がなぜ使用したのか疑問に思っているのであれば5、ここで取り上げたくない他の2つの問題に遭遇するのを避けるためです。2つの異なる理由で、完全に別々に作成された5が最終的に同じ数のインスタンスです5。ただし、完全に個別に作成されたobject()はできません。)


では、なぜa.foo.append(5)Alexの例ではb.fooが影響するのa.foo = 5ですか?私の例では影響しないのですか?さて、a.foo = 5アレックスの例を試してみてください、そしてそれはb.fooそこに影響を与えないことに注意してください。

a.foo = 5a.foo名前にしてい5ます。これはb.foo、や、以前a.fooは参照されていた古い値のその他の名前には影響しません。*クラス属性を非表示にするインスタンス属性を作成するのは少し難しいですが、一度取得すると、複雑なことは何もありません。ここで起こっています。


うまくいけば、Alexがリストを使用した理由は明らかです。リストを変更できるという事実は、2つの変数が同じリストに名前を付けていることを示すのが簡単であること、および2つのリストがあるかどうかを知ることが実際のコードでより重要であることを意味します同じリストの2つの名前。


* C ++などの言語を使用している人の混乱は、Pythonでは値が変数に格納されないことです。値は値域に存在し、それ自体で、変数は値の単なる名前であり、割り当ては値の新しい名前を作成するだけです。それが役立つ場合は、各Python変数をのshared_ptr<T>代わりにと考えてくださいT

**一部の人々は、インスタンスが設定する場合と設定しない場合があるインスタンス属性の「デフォルト値」としてクラス属性を使用することにより、これを利用します。これは場合によっては便利ですが、混乱する可能性もありますので、注意してください。


0

もう1つ状況があります。

クラスとインスタンスの属性はDescriptorです。

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

上記は出力されます:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

クラスまたはインスタンスを介した同じタイプのインスタンスアクセスは、異なる結果を返します!

そして、私はc.PyObject_GenericGetAttrの定義と素晴らしい投稿で見つけました。

説明する

属性が、構成するクラスの辞書で見つかった場合。オブジェクトMRO、次に、ルックアップされている属性がデータ記述子を指しているかどうかを確認します(これは__get____set__メソッドの両方を実装するクラスにすぎません)。存在する場合__get__は、データ記述子のメソッドを呼び出して(28行目から33行目)、属性ルックアップを解決します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.