
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): ... # __get__ tp_descr_get ... def __get__(self, instance, owner): ... print('I was gotten from %s (instance of %s)' ... % (instance, owner)) ... return self ... >>> class Foo: ... Shouting42 = ShoutingInteger(42) ... >>> foo = Foo() >>> 100 - foo.Shouting42 I was gotten from <__main__.Foo object at 0xb7583c8c> (instance of <class __main__.'foo'>) 58 # : ! >>> foo.Silent666 = ShoutingInteger(666) >>> 100 - foo.Silent666 -566 >>>
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> >>> # GDB Program received signal SIGTRAP, Trace/breakpoint trap. 0x0012d422 in __kernel_vsyscall () (gdb) p ((PyObject *)(0x846b06c))->ob_type->tp_dictoffset $1 = 16 (gdb) p *((PyObject **)(((char *)0x846b06c)+16)) $3 = {u'foo': u'bar'} (gdb)
新しいクラス、オブジェクトを作成し、その属性(
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
それらを調べてください。 楽しんでください。 次のシリーズでは、しばらくの間オブジェクトを残し、インタープリターの状態とストリームの状態について話します 。 面白いものになることを願っています。 しかし、彼がそうしなくても、あなたはまだこれを知る必要があります。 確かに言えることは、女の子はそのような問題に精通している男たちがひどく好きだということです。
知ってる? 女の子だけではありません。 これらの人も好きです。 一緒に楽しみましょう 。