
1.
はじめに2.
オブジェクト。 頭3.
オブジェクト。 しっぽ4.
プロセス構造前のパートでは、Pythonオブジェクトシステムの調査を開始しました。オブジェクトと正確に考えられるものと、オブジェクトがどのように仕事をするかを理解しました。 問題の検討を続けます。Pythonの内部に関する一連の記事の第3部であなたを歓迎します(まだ読んでいない場合は、第2部を読むことを強くお勧めします。そうしないと、何も理解できません)。 このエピソードでは、属性についてはまだ理解できない重要な概念について説明します。 Pythonで何かを書いたことがあるなら、それを使わなければなりませんでした。 オブジェクトの属性は、operettorを介してアクセス可能な、それに関連付けられた他のオブジェクトです
. (ピリオド)、例:
>>> my_object.attribute_name 。 属性を参照するときのPythonの動作を簡単に説明します。 この動作は、属性によってアクセス可能なオブジェクトのタイプに依存します(これはオブジェクトに関連するすべての操作に適用されることを既に理解していますか?)。
型は、そのインスタンスの属性へのアクセスを変更する特別なメソッドを記述することができます。 これらのメソッドについては、
ここで説明し
ます (すでに知っているように、必要なタイプスロットに、タイプが作成される
fixup_slot_dispatchers関数に関連付けられます...
前の投稿を
読みますか?)。 これらのメソッドは何でもできます。 タイプをCまたはPythonのどちらで記述しても、信じられないほどのリポジトリから属性を格納および返すメソッドを記述できます。必要に応じて、ISSから無線で属性を送受信したり、リレーショナルに格納することもできます。データベース。 しかし、多かれ少なかれ通常の状況では、これらのメソッドは、属性が設定されたときにオブジェクトディクショナリにキーと値のペア(属性名/属性値)の形式で属性を書き込み、要求されたときにこのディクショナリから属性を返します(または例外がスローされます)
AttributeError 。辞書に、要求された属性の名前に対応するキーがない場合。 とてもシンプルで美しいです。ご清聴ありがとうございました。おそらくここで終わります。
立つために ! 私の友人、糞便塊は回転風力発電機への迅速なアプローチを始めたばかりです。 消えるので、すべてが消えます。 私は通訳で何が起こっているのかを一緒に勉強し、通常のようにいくつかの迷惑な質問をすることを提案します。
コードを注意深く読むか、すぐにテキストの説明に進みます。
>>> print(object.__dict__) {'__ne__': <slot wrapper '__ne__' of 'object' objects>, ... , '__ge__': <slot wrapper '__ge__' of 'object' objects>} >>> object.__ne__ is object.__dict__['__ne__'] True >>> o = object() >>> o.__class__ <class 'object'> >>> oa = 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'object' object has no attribute 'a' >>> o.__dict__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'object' object has no attribute '__dict__' >>> class C: ... A = 1 ... >>> C.__dict__['A'] 1 >>> CA 1 >>> o2 = C() >>> o2.a = 1 >>> o2.__dict__ {'a': 1} >>> o2.__dict__['a2'] = 2 >>> o2.a2 2 >>> C.__dict__['A2'] = 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'dict_proxy' object does not support item assignment >>> C.A2 = 2 >>> C.__dict__['A2'] is C.A2 True >>> type(C.__dict__) is type(o2.__dict__) False >>> type(C.__dict__) <class 'dict_proxy'> >>> type(o2.__dict__) <class 'dict'>
これを人間の言語に翻訳しましょう:
object (これは忘れてしまった場合、これは最も単純な組み込み型です)には、私たちが見るように辞書があり、属性を通してアプローチできるものはすべて
object.__dict__にあるものと同じです。
タイプobject (たとえば、オブジェクトo )のインスタンスは追加属性の定義をサポートせず、通常__dict__持たないが、既存の属性へのアクセスをサポートする (
o.__class__ 、
o.__hash__など;これらのコマンドは何かを
返します )。 その後、新しい
Cクラスを作成し、
objectから継承し、
A属性を追加し、
CAおよび
C.__dict__['A']介してアクセスできることを確認しました。 次に、クラス
C o2インスタンスを作成し、属性の定義が
__dict__変更し、その逆に
__dict__を変更すると属性に影響することを確認しました。 その後、
属性定義( C.A2 )はC.A2ますが、 C.A2クラスが読み取り専用であることを知って驚きました。 最後に、
インスタンスとクラスの__dict__オブジェクトには異なるタイプ (おなじみの
dictと神秘的な
dict_proxyあることが
dict_proxyました。 これだけでは不十分な場合は、前の部分のパズルを思い出してください:純粋な
object (たとえば
o )の子孫が
__dict__持たず、
Cが
objectを拡張しても重要な
objectを追加
しない場合、クラスCインスタンス( o2ええ、それはどんどん変です! しかし、心配しないで、すべてに時間があります。 まず、
__dict__型の実装を検討します。
PyTypeObjectの定義を見ると(読むことを強くお勧めします!)、辞書へのポインタを受け入れる準備ができている
tp_dictスロットを見ることができます。 このスロットはすべてのタイプに対応しています。
./Objects/typeobject.cを呼び出すと、ディクショナリーが配置されます。これは、インタープリターが初期化されるときに発生します(
Py_Initialize ?この関数は
_Py_ReadyTypesを呼び出します
PyType_Ready帰国前の新生児のタイプごとに)。 実際、
class文で定義する各名前
__dict__新しい型の
__dict__表示されます(行
./Objects/typeobject.c type->tp_dict = dict = PyDict_Copy(dict); )。 型もオブジェクトであることを忘れないでください。 また、タイプ-
typeがあり、適切な方法で属性にアクセスできる機能を備えたスロットがあります。 これらの関数は、各タイプが持つディクショナリを使用し、
tp_dictポイントして属性を保存/アクセスします。 したがって、タイプ属性の呼び出しは、実際には、タイプ構造によって示されるタイプ
typeインスタンスのプライベート辞書の呼び出しです。
class Foo: bar = "baz" print(Foo.bar)
この例では、最後の行はtype属性の呼び出しを示しています。 この場合、
bar属性を見つけるために、
Fooクラスの属性アクセス関数(
tp_getattroによって参照される)が呼び出されます。 属性を定義および削除するときにもほぼ同じことが起こります(インタープリターの場合、「削除」は値
NULL設定するだけです)。 これまでのところすべてが明確になっていることを願っていますが、その間に属性の使用について議論してきました。
インスタンス属性へのアクセスを検討する前に、あまり知られていない(しかし非常に重要な)概念:
記述子について説明します。 インスタンス属性にアクセスするとき、記述子は特別な役割を果たします。それが何であるかを明確にする必要があります。 タイプ(
tp_descr_getおよび/または
tp_descr_set )の1つまたは2つのスロットがゼロ以外の値で満たされている場合、オブジェクトは記述子と見なされます。 これらのスロットは、特別なメソッド
__get__ 、
__set__および
__delete__関連付けられています(たとえば、
tp_descr_getスロットに接続してこのクラスのオブジェクトを作成する
__get__メソッドでクラスを定義する場合、このオブジェクトは記述子になります)。 最後に、
tp_descr_setスロットがゼロ以外の値で満たされている場合、オブジェクトは
データ記述子と見なされます。 後で説明するように、記述子は属性にアクセスする際に重要な役割を果たします。必要なドキュメントへの説明とリンクをいくつか提供します。
そこで、記述子とは何かを理解し、型属性へのアクセスがどのように発生するかを理解しました。 しかし、ほとんどのオブジェクトはタイプではありません。 それらの型は
typeではなく、より普遍的なもの、たとえば
int 、
dictまたはカスタムクラスです。 これらはすべて、タイプで定義されているか、タイプの作成時にタイプの親から継承された属性にアクセスするためのユニバーサル関数に依存しています(このトピック、スロットの継承、「
ヘッド 」で説明しました)。 属性にアクセスする汎用機能(
PyObject_GenericGetAttr )のアルゴリズムは次のようになります。
- インスタンスタイプディクショナリおよびすべてのタイプペアレントのディクショナリを検索します。 データ記述子が見つかった場合、その
tp_descr_get関数を呼び出して結果を返します。 何か他のものが見つかった場合、念のためにこれを覚えておいてください(たとえば、名前Xの下)。 - オブジェクトの辞書を検索し、見つかった場合は結果を返します。
- オブジェクトのディクショナリに何も見つからなかった場合、インストールされている場合はXを確認します Xが記述子の場合、その
tp_descr_get関数を呼び出して結果を返します。 Xが通常のオブジェクトの場合、それを返します。 - 最後に、何も見つからなかった場合、
AttributeError例外をスローします。
これで、属性としてアクセスしたときに記述子がコードを実行できることがわかりました(つまり、
foo = oaまたは
oa = fooを記述すると、コードが実行されます)。 Pythonの「マジック」機能の一部を実装するために使用される強力な機能。 インスタンス記述子よりも優先されるため、データ記述子はさらに強力です(クラス
Cオブジェクト
o 、クラス
Cにデータ記述子
fooがあり、
oに属性
fooがある場合、
o.foo実行される
o.foo結果は記述子を返します)。 記述子とは何か、また
どのように読む
か 。 特に最初のリンク( "what")をお勧めします。最初は書くのは気が進まないという事実にもかかわらず、慎重に思慮深く読んだ後、あなたはそれが私の話よりもずっと簡単で短いことに気付くでしょう。 また、Python 2.xの記述子
について説明している
Raymond Hettingerの素晴らしい記事も読んでください。 関連のないメソッドの
削除を除けば、この記事はバージョン3.xに関連するため、読むことをお勧めします。 記述子は非常に重要な概念です。リストされたリソースを理解し、アイデアに触発されるために、リストされたリソースの調査に時間をかけることをお勧めします。 ここでは、簡潔にするために詳細には触れませんが、インタープリターでの動作の例を(
非常に簡単に)示します。
>>> class ShoutingInteger(int): ...
Pythonでのオブジェクト指向の継承について完全に理解したことに注意してください。 属性の検索はオブジェクトのタイプから始まり、すべての親では
Oクラス
C1オブジェクト
O属性
Aアクセスし、
C2から継承し、
C3から継承して、
Oと
C1両方から
Aを返すことができること、および
C2および
C3 。メソッド解決の特定の順序によって決定され
ます 。これについては、
ここで詳しく説明し
ます 。 属性をスロット継承と一緒に解決するこの方法は、Pythonの継承機能のほとんどを説明するのに十分です(ただし、通常、悪魔は詳細にあります)。
今日多くのことを学びましたが、オブジェクト辞書へのリンクがどこに保存されているかはまだ不明です。 すでに
PyObjectの定義を見てきましたが、確かに同様の辞書へのポインタはありません。 そこになければ、どこに? 答えはかなり予想外です。
PyTypeObjectを注意深く見ると(これは良い娯楽です!毎日読んでください!)、
tp_dictoffsetというフィールドに気づくでしょう。 このフィールドは、タイプインスタンスに割り当てられたC構造体のバイトオフセットを定義します。 このオフセットには、通常のPython辞書へのポインターがあります。 通常の条件下では、新しいタイプを作成するときに、そのタイプのインスタンスに必要なメモリのサイズが計算され、このサイズは純粋な
PyObjectよりも大きくなり
PyObject 。 通常、辞書へのポインタを格納するために余分なスペースが使用されます(これはすべて
./Objects/typeobject.c発生し
./Objects/typeobject.c :
type_new 、行
may_add_dict = base->tp_dictoffset == 0;から読み取り
may_add_dict = base->tp_dictoffset == 0; )。
gdbを使用すると 、このスペースに簡単に侵入してオブジェクトのプライベート辞書を見ることができます。
>>> class C: pass ... >>> o = C() >>> o.foo = 'bar' >>> o <__main__.C object at 0x846b06c> >>>
新しいクラス、オブジェクトを作成し、その属性(
o.foo = 'bar' )を決定し、
gdbに入り、オブジェクトタイプ(
C )を逆参照し、
tp_dictoffset (16)を見つけ、このオフセットにあるものをチェックしましたオブジェクトのC構造。 当然のことながら、
barの値を示す1つのキー
foo持つオブジェクトディクショナリが見つかりました。 当然、
objectなどの
__dict__を持たないタイプの
tp_dictoffsetをチェックする
tp_dictoffsetゼロが見つかります。 グースバンプス?
タイプディクショナリとインスタンスディクショナリは似ていますが、実装が大きく異なるため、混乱を招く可能性があります。 さらにいくつかの謎が残っています。 不足しているものをまとめて判断しましょう:
objectから継承した空のクラス
Cを定義し、このクラスのオブジェクト
o作成し、オフセット
tp_dictoffsetによって辞書へのポインターに追加メモリを割り当てます(場所は最初から割り当てられますが、辞書は最初(のみ)処理;ここに不正があります...)。 次に
o.__dict__で実行します。バイトコードは
LOAD_ATTRコマンドでコンパイルされ、
PyObject_GetAttr関数が呼び出され
PyObject_GetAttr関数はオブジェクトタイプ
oを逆参照し、上記の標準属性検索プロセスを開始し、
PyObject_GenericGetAttrで実装された
tp_getattroスロットを見つけ
PyObject_GenericGetAttr 。 その結果、これがすべて発生した後、オブジェクトの辞書を返す
ものは
何ですか? 辞書の保存
場所はわかっていますが、
__dict__はないことがわかります。したがって、鶏と卵の問題が発生します。辞書自体にない場合、
__dict__に戻ると辞書は何を返しますか。
オブジェクトの辞書よりも優先されるものはハンドルです。 参照:
>>> class C: pass ... >>> o = C() >>> o.__dict__ {} >>> C.__dict__['__dict__'] <attribute '__dict__' of 'C' objects> >>> type(C.__dict__['__dict__']) <class 'getset_descriptor'> >>> C.__dict__['__dict__'].__get__(o, C) {} >>> C.__dict__['__dict__'].__get__(o, C) is o.__dict__ True >>>
わあ!
getset_descriptor (ファイル
./Objects/typeobject.c )と呼ばれるものがあり、記述子プロトコルを実装し、
./Objects/typeobject.c型のオブジェクト内になければならない特定の関数グループがあることが
getset_descriptorます。 この記述子は、このタイプの
o.__dict__オブジェクトへのアクセス試行をすべてインターセプトし、必要なものをすべて返します。この場合、それは
o tp_dictoffsetをオフセットすることで辞書へのポインターになります。 これは
dict_proxy少し前に
dict_proxyを見た理由も説明しています。
tp_dictに簡単な辞書へのポインタがある場合、何も
tp_dictないオブジェクトにラップされているのはなぜですか? これにより、タイプ
type __dict__ハンドルが作成されます。
>>> type(C) <class 'type'> >>> type(C).__dict__['__dict__'] <attribute '__dict__' of 'type' objects> >>> type(C).__dict__['__dict__'].__get__(C, type) <dict_proxy object at 0xb767e494>
このハンドルは、読み取り専用であることを除き、通常の辞書の動作をシミュレートする単純なオブジェクトで辞書をラップする関数です。
__dict__型でのユーザーの介入を防ぐことがなぜそれほど重要なのですか? タイプ名前空間には、
__sub__などの特別なメソッドが含まれる場合があるため
__sub__ 。 特別なメソッドを使用して型を作成する場合、または属性を介して型で定義する場合、
update_one_slot関数が
update_one_slot 。これにより、たとえば前の投稿の減算操作で発生したように、これらのメソッドが型スロットに関連付けられます これらのメソッドを
__dict__型に直接追加できる場合、それらはスロットに関連付けられず、必要なものに似た型を取得します(たとえば、辞書に
__sub__があります)が、動作が異なります。
私たちは長い間2000単語の
__slots__を越えましたが、それを超えると読者の注意は急速に
__slots__ 、私はまだ
__slots__について話していません。 あなた
自身で 、向こう見ずに読んでみてはどうですか? あなたの自由でそれらだけで対処するすべてがあります! 指定されたリンクでドキュメントを読み、インタープリターで
__slots__を少し
__slots__で、ソースを見て、
gdbそれらを調べてください。 楽しんでください。 次のシリーズでは、しばらくの間オブジェクトを残し
、インタープリターの
状態とストリームの状態について話します 。 面白いものになることを願っています。 しかし、彼がそうしなくても、あなたはまだこれを知る必要があります。 確かに言えることは、女の子はそのような問題に精通している男たちがひどく好きだということです。
知ってる? 女の子だけではありません。 これらの人も好きです。 一緒に楽しみましょう 。