私はDescriptor HowTo Guideにディスクリプタについて読んでいる、と私はこの文で困惑している:インスタンスの辞書は、データ記述子と同じ名前のエントリを持っている場合Pythonの属性とディスクリプタ
、データ記述子が優先されます。
辞書には、同じ名前の2つの項目(通常のエントリとデータ記述子)が含まれていますか?または、記述子である属性は__dict__
に格納されていませんか?
私はDescriptor HowTo Guideにディスクリプタについて読んでいる、と私はこの文で困惑している:インスタンスの辞書は、データ記述子と同じ名前のエントリを持っている場合Pythonの属性とディスクリプタ
、データ記述子が優先されます。
辞書には、同じ名前の2つの項目(通常のエントリとデータ記述子)が含まれていますか?または、記述子である属性は__dict__
に格納されていませんか?
インスタンスがインスタンス名前空間(そうinstance.__dict__
)で生活をしながら、属性データ記述子は、クラス名前空間に住んでいます。これらは2つの別々の辞書であるため、ここでは矛盾はありません。だから、インスタンスbar
の名前foo
のための任意の属性を参照するために、Pythonはまた(以下C
命名type(bar)
、)それのクラスを見て
は、次の順序で:
C.foo
見上げています。 データ記述子の場合は、これがルックアップが終了する場所です。 C.foo.__get__(bar, C)
が返されます。さもなければ、Pythonはこの結果をステップ3に保存します(これを2回見ても意味がありません)。
C.foo
が存在しないか、または標準属性の場合、Pythonはbar.__dict__['foo']
を探します。存在する場合は返されます。 C.foo
がデータ記述子である場合、この部分に到達しないことに注意してください。
bar.__dict__['foo']
が存在せず、C.foo
が存在する場合は、C.foo
が使用されます。 C.foo
が(非データの)記述子の場合は、C.foo.__get__(bar, C)
が返されます。
(C.foo
が本当にC.__dict__['foo']
が、簡単のために、私は上記のクラスの記述子へのアクセスを無視してきたことに注意してください)。
おそらく具体的な例が役立ちます。ここでは2つの記述子があり、一つはデータ記述子(__set__
方法があるが)であり、他方はありませんデータ記述子:
>>> class DataDesc(object):
... def __get__(self, inst, type_):
... print('Accessed the data descriptor')
... return 'datadesc value'
... def __set__(self, inst, value):
... pass # just here to make this a data descriptor
...
>>> class OtherDesc(object):
... def __get__(self, inst, type_):
... print('Accessed the other, non-data descriptor')
... return 'otherdesc value'
...
>>> class C(object):
... def __init__(self):
... # set two instance attributes, direct access to not
... # trigger descriptors
... self.__dict__.update({
... 'datadesc': 'instance value for datadesc',
... 'otherdesc': 'instance value for otherdesc',
... })
... datadesc = DataDesc()
... otherdesc = OtherDesc()
...
>>> bar = C()
>>> bar.otherdesc # non-data descriptor, the instance wins
'instance value for otherdesc'
>>> bar.datadesc # data descriptor, the descriptor wins
Accessed the data descriptor
'datadesc value'
は、コードスニペット、次の点を考慮:このコード
class X:
@property
def x(self):
return 2
x1 = X()
x1.__dict__['x'] = 1
print(x1.x)
データ記述子(クラスに定義されている)がインスタンス辞書より優先されるため、2が出力されます。
これの根拠は何ですか?私は直感的に同じ名前のクラス属性をシャドウするインスタンス属性を期待しています。 –
@SirVisto:これにより、データ記述子がアクセスを完全に制御できるようになり(設定と削除を含む)。インスタンスdictが優先された場合、 'del inst.foo'はインスタンスに到達する*時には*データ記述子になることがあります。設定するには、 'inst.foo = 'value''を実行したときに何が起こるべきですか?インスタンス属性がすでに存在するかどうかは重要ですか?その後、誰が設定を処理する責任がありますか?一貫して*常に*データ記述子を担当させることで、これらの質問をすべて明確かつ一貫して解決できます。 –
@SirVisto:もう一つの動機は、インスタンス内でいくつかのクラス属性が誤って上書きされないようにすることです。たとえば、 '__class__'属性はインスタンス上に設定しないでください。これは、対応するクラスを見つけることができなくなるため、' __class__'はクラスのデータ記述子です。 –
いいえ、データ記述子はクラス辞書に含まれています。 "Descriptor HowTo Guide"はクラス辞書の前にインスタンス辞書が検索されていることを示唆しているようです: "たとえば、axは__ dict __ ['x']で始まる検索チェーンを持っています。そして(a).__ dict __ [ 'x']、メタクラスを除いたタイプ(a)の基底クラスを継承します。 " –
@SirVistoハウツーはそこで単純化しています。画像からデータ記述子を取り出した場合、その順序は完全に正しいです。その時点でデータ記述子を導入すると、話が複雑になります。 –
実際には、記事ではこれを後で明確にしています。「インプリメンテーションは、データ記述子をインスタンス変数より優先させ、インスタンス変数を非データ記述子よりも優先させ、__getattr __()があれば優先順位を低く設定します。 –