属性参照

通常の属性参照

x.nameでxの属性nameを参照した場合、以下の手順で検索される。

  • xがインスタンスオブジェクトの場合
    • 'name'がx.__dict__のキーである場合、x.nameはx.__dict__['name']の値を返す。
    • それ以外の場合、x.nameは検索をxのクラスに委譲する(つまり、x.__class__.nameと同じように機能する)。
  • xがクラスオブジェクトの場合
    • 'name'がx.__dict__のキーである場合、x.nameはx.__dict__['name']の値を返す。
    • それ以外の場合、x.nameは検索をCの基底クラスに委譲する(つまり、x.__bases__をループにかけ、それぞれごとにnameを検索する)
  • 以上で見つからなかった場合
    • 特殊メソッド__getattr__が定義もしくは継承されている場合、x.__getattr__['name']が呼び出される。__getattr__は、属性値を返すかAttributeError例外を発生させる必要がある。
    • それ以外の場合、AttributeError例外が発生する。
  • 新スタイルクラスで特殊メソッド__getattribute__が定義されている場合
    • 上記のルールとは無関係にx.__getattribute__['name']が呼び出される。__getattribute__は、属性値を返すかAttributeError例外を発生させる必要がある。
>>> class B:
...     a, b = 1, 2
...     def foo(self):
...         print 'foo in B'
...     def bar(self):
...         print 'bar in B'
...
>>> class D(B):
...     b, c, d = 22, 33, 44
...     def bar(self):
...         print 'bar in D'
...     def baz(self):
...         print 'baz in D'
...
>>> x = D()
>>> x.__dict__.keys()  # インスタンスxの属性は存在しない
[]
>>> D.__dict__.keys()  # クラスDの属性
['__module__', 'b', 'bar', 'd', 'baz', 'c', '__doc__']
>>> B.__dict__.keys()  # クラスBの属性
['a', '__module__', 'b', 'bar', 'foo', '__doc__']
>>> x.foo  # fooは参照可能
<bound method D.foo of <__main__.D instance at 0x00E3FB70>>
>>> del x.foo  # fooはxの属性でないので削除不可能
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: D instance has no attribute 'foo'
>>> del B.foo  # fooはBの属性なので削除可能
>>> x.foo  # fooは削除されたので参照不可能
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: D instance has no attribute 'foo'
>>>
>>> del x.__dict__  # __dict__は削除できない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __dict__ must be set to a dictionary
>>> x.__dict__ = []  # __dict__は辞書以外の値は設定できない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __dict__ must be set to a dictionary
__slots__属性

属性参照とは関係ないが、新スタイルクラスでは__slots__により属性を制限できる。

>>> class C(object):
...     __slots__ = ['v1', 'v2']
...
>>> x = C()
>>> x.v1 = 1
>>> x.v1, C.v1
(1, <member 'v1' of 'C' objects>)
>>> x.v2  # まだ初期化していないので参照できない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: v2
>>> x.v3 = 3  # __slots__に定義していない属性には代入できない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'v3'
>>> x.__dict__  # __slots__を定義すると、__dict__が生成されない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute '__dict__'

また、__slots__ 宣言が動作するのは、定義が行われたクラスだけに限られる。サブクラスでは、__slots__ を定義しない限り __dict__ を持つ。

ビルトインクラスの属性参照

ビルトインクラスは__dict__属性や__slots__属性を持っていないので、あらかじめ定義されている属性を参照することしかできない。新しい属性を追加したり、定義されている属性を削除することはできない。

>>> L = list()
>>> L.__dict__  # __dict__属性は存在しない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__dict__'
>>> L.x = 1  # 属性を追加できない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'x'
>>> del L.sort  # 定義されている属性を削除できない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object attribute 'sort' is read-only

継承すれば通常のクラスと同様に__dict__属性を持ち、自由に属性を制御できる。

>>> class List(list): pass
...
>>> L = List()
>>> L.__dict__
{}
>>> L.x = 1