Pythonクラスの__dict __.__ dict__属性とは何ですか?


91
>>> class A(object): pass
... 
>>> A.__dict__
<dictproxy object at 0x173ef30>
>>> A.__dict__.__dict__
Traceback (most recent call last):
  File "<string>", line 1, in <fragment>
AttributeError: 'dictproxy' object has no attribute '__dict__'
>>> A.__dict__.copy()
{'__dict__': <attribute '__dict__' of 'A' objects> ... }
>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects> # What is this object?

私がそうするならA.something = 10、これは入りA.__dict__ます。何をしている、これは<attribute '__dict__' of 'A' objects>で見つけA.__dict__.__dict__、それが何かをするときが含まれていますか?


11
より適切な変数の例はでしょうive。少なくとも、これはこれ以上のA.__dict__['ive']質問になります;)私は自分自身を見るでしょう
Joakim

回答:


109

まず、とA.__dict__.__dict__は異なりA.__dict__['__dict__']、前者は存在しません。後者は、__dict__クラスのインスタンスが持つ属性です。これは、特定のインスタンスの属性の内部ディクショナリを返す記述子オブジェクトです。つまり、__dict__オブジェクトの属性はオブジェクトの__dict__に格納できないため、クラスで定義された記述子を介してアクセスされます。

これを理解するには、記述子プロトコルのドキュメントを読む必要があります

ショートバージョン:

  1. クラスのインスタンスではA、へのアクセスinstance.__dict__が提供さA.__dict__['__dict__']vars(A)['__dict__']ます。これはと同じです。
  2. クラスAの場合、へのアクセスA.__dict__type.__dict__['__dict__'](理論的には)によって提供され、と同じvars(type)['__dict__']です。

長いバージョン:

クラスとオブジェクトはどちらも、(クラスまたはメタクラスのを介して実装された)属性演算子__getattribute__と、__dict__が使用する属性/プロトコルの両方を介して属性へのアクセスを提供しますvars(ob)

通常のオブジェクトの場合、__dict__オブジェクトはdict属性を格納する別のオブジェクトを作成し、__getattribute__最初にそのオブジェクトにアクセスしてそこから属性を取得しようとします(記述子プロトコルを使用してクラス内の属性を検索する前、およびを呼び出す前__getattr__)。__dict__クラスの記述子は、この辞書へのアクセスを実装します。

  • x.nameオーダーのものを試すと同等です:x.__dict__['name']type(x).name.__get__(x, type(x))type(x).name
  • x.__dict__ 同じことをしますが、明白な理由で最初のものをスキップします

をインスタンスに格納すること__dict__は不可能であるため、代わりに記述子プロトコルを介して直接アクセスされ、インスタンスの特別なフィールドに格納されます。instance__dict__

同様のシナリオがクラスにも当てはまりますが、それら__dict__は辞書のふりをしている(ただし内部的にはない可能性があります)特別なプロキシオブジェクトであり、変更したり、別のオブジェクトに置き換えたりすることはできません。このプロキシを使用すると、とりわけ、クラスのいずれかのベースで定義されていない、そのクラスに固有の属性にアクセスできます。

デフォルトでvars(cls)は、空のクラスのは3つの記述子__dict__を保持します- __weakref__によって内部的に使用されるインスタンスの属性とweakrefクラスのdocstring を格納するためです。定義すると、最初の2つはなくなる可能性があります__slots__。次に__dict____weakref__属性はありませんが、代わりに各スロットに単一のクラス属性があります。その場合、インスタンスの属性はディクショナリに格納されず、それらへのアクセスはクラスのそれぞれの記述子によって提供されます。


最後に、とA.__dict__は異なる不整合はA.__dict__['__dict__']、属性__dict__が例外的にで検索されないvars(A)ためです。そのため、これに当てはまることは、使用する他のどの属性にも当てはまりません。たとえば、A.__weakref__はと同じですA.__dict__['__weakref__']。この不整合が存在しなかった場合、使用A.__dict__は機能せず、vars(A)代わりに常に使用する必要があります。


6
詳細な回答ありがとうございます。何度か読まなくてはなりませんでしたが、Pythonの新しい詳細をたくさん学んだので、久しぶりだと思います。
porgarmingduod

__dict__オブジェクトの属性をオブジェクトの属性に正確に格納できないのはなぜ__dict__ですか?
zumgruenenbaum

2
@zumgruenenbaum __dict__はすべてのインスタンス属性を格納するためのものであるため、フォームの属性アクセスobj.xは最終的にオブジェクトの__dict__、つまりで検索されますobj.__dict__['x']。これで__dict__、記述子として実装されなかった場合、アクセスobj.__dict__するにはとしてルックアップする必要があるため、無限再帰が発生しますobj.__dict__['__dict__']。記述子はこの問題を回避します。
a_guest

10

A.__dict__A属性を格納するディクショナリであるため、A.__dict__['__dict__']はその同じA.__dict__属性への直接参照です。

A.__dict__それ自体への(種類の)参照が含まれています。「種類」の部分が、式A.__dict__dictproxy通常ではなくを返す理由dictです。

>>> class B(object):
...     "Documentation of B class"
...     pass
...
>>> B.__doc__
'Documentation of B class'
>>> B.__dict__
<dictproxy object at 0x00B83590>
>>> B.__dict__['__doc__']
'Documentation of B class'

9
A.__dict__['__dict__']はへの参照ではありませんA.__dict____dict__インスタンスの属性を実装します。これを自分で試すにA.__dict__['__dict__'].__get__(A(), A)は、の属性を返しますがA()A.__dict__['__dict__'].__get__(A, type)失敗します。
Rosh Oxymoron、2011

10

いくつか探索してみましょう!

>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects>

なんだろう?

>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>

getset_descriptorオブジェクトにはどのような属性がありますか?

>>> type(A.__dict__["__dict__"]).__dict__
<dictproxy object at 0xb7efc4ac>

そのコピーを作成することにより、dictproxyいくつかの興味深い属性、特に__objclass__およびを見つけることができます__name__

>>> A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__
(<class '__main__.A'>, '__dict__')

それで、おそらく属性の名前__objclass__への参照でAあり、__name__単なる文字列'__dict__'ですか?

>>> getattr(A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__) == A.__dict__
True

ありました! A.__dict__['__dict__']は、を再び参照できるオブジェクトですA.__dict__


PEP 252は、それ__objclass__がこの属性を定義したクラスであり、そのクラスの属性ではないと述べています。これはあなたのgetattr例を不正確にします。より正確なものは次のgetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
とおり

1
@RoshOxymoron @AndrewClarkの式とはKeyError: '__dict__'逆に、式が発生します。
Maggyero

9

次の簡単な例を試して、これについてさらに理解してください。

>>> class A(object): pass
... 
>>> a = A()
>>> type(A)
<type 'type'>
>>> type(a)
<class '__main__.A'>
>>> type(a.__dict__)
<type 'dict'>
>>> type(A.__dict__)
<type 'dictproxy'>
>>> type(type.__dict__)
<type 'dictproxy'>
>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>
>>> type(type.__dict__['__dict__'])
<type 'getset_descriptor'>
>>> a.__dict__ == A.__dict__['__dict__'].__get__(a)
True
>>> A.__dict__ == type.__dict__['__dict__'].__get__(A)
True
>>> a.__dict__ == type.__dict__['__dict__'].__get__(A)['__dict__'].__get__(a)
True

上記の例から、クラスオブジェクトの属性はクラスによって格納されているようで、クラスの属性はメタクラスであるクラスによって格納されているようです。これは以下によっても検証されます。

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True

1
奇妙なことに、2番目の比較でisが置き換えられ==た場合、つまりA.__dict__ is type.__dict__['__dict__'].__get__(A)、結果はFalsepython 2.7.15+と3.6.8の両方にあります。
Arne Vogel
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.