内部からのPython。 オブジェクト しっぽ

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 )のアルゴリズムは次のようになります。



  1. インスタンスタイプディクショナリおよびすべてのタイプペアレントのディクショナリを検索します。 データ記述子が見つかった場合、そのtp_descr_get



    関数を呼び出して結果を返します。 何か他のものが見つかった場合、念のためにこれを覚えておいてください(たとえば、名前Xの下)。
  2. オブジェクトの辞書を検索し、見つかった場合は結果を返します。
  3. オブジェクトのディクショナリに何も見つからなかった場合、インストールされている場合はXを確認します Xが記述子の場合、そのtp_descr_get



    関数を呼び出して結果を返します。 Xが通常のオブジェクトの場合、それを返します。
  4. 最後に、何も見つからなかった場合、 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



それらを調べてください。 楽しんでください。 次のシリーズでは、しばらくの間オブジェクトを残し、インタープリター状態とストリームの状態について話します 。 面白いものになることを願っています。 しかし、彼がそうしなくても、あなたはまだこれを知る必要があります。 確かに言えることは、女の子はそのような問題に精通している男たちがひどく好きだということです。



知ってる? 女の子だけではありません。 これらの人も好きです。 一緒に楽しみましょう



All Articles