web-dev-qa-db-ja.com

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

次の間に意味のある区別はありますか?

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

vs.

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

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

126
Dan Homerick

パフォーマンスの考慮事項以外に、重要なセマンティックの違いがあります。クラス属性の場合、参照されるオブジェクトは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    
[]
138
Alex Coventry

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

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

36
Peter Shinners

これは非常に良い post であり、以下のように要約します。

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

そして視覚的な形で

enter image description here

クラス属性の割り当て

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

    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
    
27
zangw

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

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
_

(なぜ_5_の代わりにobject()を使用したのか不思議に思っているなら、それは私がここに入りたくない他の2つの問題にぶつからないようにするためです。理由として、完全に個別に作成された_5_ sは、番号_5_の同じインスタンスになることがありますが、完全に個別に作成されたobject() sはできません。)


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

_a.foo = 5_は、単に_a.foo_を_5_の名前にしています。 _b.foo_、または_a.foo_が参照していた古い値の他の名前には影響しません。*クラス属性を非表示にするインスタンス属性を作成するのは少し難しい*しかし、それを取得したら、ここでは複雑なことは何も起きていません。


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


* C++のような言語から来た人にとっての混乱は、Pythonでは値が変数に保存されないことです。値はバリューランドに存在し、それ自体で変数は値の単なる名前であり、割り当ては値の新しい名前を作成するだけです。役立つ場合は、各Python変数をTではなく_shared_ptr<T>_として考えてください。

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

19
abarnert

Alex Coventryが言ったことを詳しく述べたところ、別のAlex(Martelli)がcomp.lang.python数年前のニュースグループ。彼は、(インスタンス変数を使用して)人が意図したものと得たものの意味の違いを調べます。

http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?hl=en

2
torial

もう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 __== '__main__':
    main()

上記が出力されます:

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

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

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

説明する

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

0
SimonShyu