![](https://habrastorage.org/getpro/habr/post_images/0b5/22b/bd6/0b522bbd67604b71187629990ede7ef4.jpg)
2. オブジェクト。 頭
3. オブジェクト。 しっぽ
4. プロセス構造
Pythonの内部を引き続き理解しています。 前回 、Pythonが単純なプログラムをどのように消化するかを学びました。 今日、そのオブジェクトシステムのデバイスの研究を開始します。
前回のエピソードで書いたように(偶然にも成功したことが判明しました。皆さんのおかげで、あなたの意見やコメントは文字通り私を動かします!)-今日の投稿はPython 3.xでのオブジェクトの実装に当てられています 。 最初は簡単なトピックだと思いました。 しかし、投稿を書く前に読んでいたすべてのコードを読んだとしても、Pythonオブジェクトシステムは...ええと、「シンプル」とはほとんど言えません(完全に理解したとは言えません)。 しかし、私はオブジェクトの実装が最初から良いトピックであるとさらに確信しました。 次の投稿では、それがどれほど重要かを見ていきます。 同時に、Pythonのベテランであっても、それを完全に理解している人はほとんどいないと思います。 オブジェクトは他のすべてのPythonと緩やかに接続されています(投稿を書くとき、。/ Pythonを少し見て、。
./Objects
と
./Include
)。 オブジェクトの実装は、他のすべてとまったく接続されていないかのように考える方が簡単に思えました。 オブジェクトサブシステムを作成するためのユニバーサルC APIであるかのように。 おそらく、あなたがこのように考えることもより簡単になるでしょう。これは、これらの構造を管理するための構造と機能のセットにすぎないことを忘れないでください。
Pythonのすべてはオブジェクトです:数値、辞書、カスタムクラスと組み込みクラス、スタックフレーム、コードオブジェクト。 メモリ位置へのポインタをオブジェクトと見なすためには、構造
./Include/object.h
定義されている少なくとも2つのフィールドが必要です:
PyObject
:
typedef struct _object { Py_ssize_t ob_refcnt; struct _typeobject *ob_type; } PyObject;
多くのオブジェクトは必要なフィールドを追加することでこの構造を拡張しますが、これらの2つのフィールドは参照カウントとタイプ (リンクを追跡するために特別なデバッグビルドに追加されます)
参照カウンタは、他のオブジェクトがこのオブジェクトを参照する回数を示す数値です。 コード
>>> a = b = c = object()
では、空のオブジェクトが初期化され、
b
c
3つの異なる名前に関連付けられてい
c
。 各名前はオブジェクトへの新しいリンクを作成しますが、オブジェクトは一度作成されます。 オブジェクトを新しい名前でリンクするか、オブジェクトをリストに追加すると、新しいリンクが作成されますが、新しいオブジェクトは作成されません! このテーマについてはまだ多くのことが言われていますが、これはオブジェクトシステムよりもガベージコレクションに関連しています。 ここでこの質問を解析するのではなく、それについて別の投稿を書くほうがいいでしょう。 しかし、このトピックを去る前に、マクロ
./Include/object.h
を理解するのが簡単になったと言います。最初の部分で出会った
./Include/object.h
です
Py_DECREF
デクリメントするだけ
ob_refcnt
(
ob_refcnt
値が0の場合はリソースを解放します) 。 とりあえず、参照カウントを終了しましょう。
オブジェクトの型へのポインター、Pythonオブジェクトモデルの中心概念である
ob_type
を解析することに変わりはあり
ob_type
(覚えておいてください:3番目のPythonでは、 型とクラスは本質的に同じです。 歴史的な理由から、これらの用語の使用はコンテキストに依存します)。 各オブジェクトには、オブジェクトの存続期間中に変更されないタイプが1つしかありません(タイプは非常にまれな状況で変更される可能性があります。このタスクにはAPIがありません。タイプを変更するオブジェクトを扱った場合、この記事はほとんど読みません)。 おそらくもっと重要なのは、オブジェクトのタイプ(およびオブジェクトのタイプのみ )によって、それで何ができるかが決まることです(この段落の後のスポイラーの例)。 最初の部分から覚えているように、減算操作を実行すると、オペランドのタイプに関係なく、同じ関数が呼び出されます(整数、整数と小数、または辞書から例外を減算するなどの完全な不条理)。
コードを表示
# , , , >>> class Foo(object): ... "I don't have __call__, so I can't be called" ... >>> class Bar(object): ... __call__ = lambda *a, **kw: 42 ... >>> foo = Foo() >>> bar = Bar() >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Foo' object is not callable >>> bar() 42 # __call__? >>> foo.__call__ = lambda *a, **kw: 42 >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Foo' object is not callable # Foo? >>> Foo.__call__ = lambda *a, **kw: 42 >>> foo() 42 >>>
最初は奇妙に思えます。 単一の関数で渡されるオブジェクトの種類をどのようにサポートできますか?
void *
ポインターを取得できます(実際、データに対しても不透明な
PyObject *
ポインターを取得し
PyObject *
)が、受信した引数の処理方法をどのように決定しますか? 答えはオブジェクトのタイプにあります。 タイプはオブジェクトでもあります(参照カウントと独自のタイプがあり、ほとんどのタイプのタイプは
type
)が、2つのメインフィールドに加えて、他の多くのフィールドが含まれています。 ここでその分野の構造定義と記述を研究してください 。 定義自体は
./Include/object.h
ます。 記事を読んでいるときに彼を参照することをお勧めします。 タイプのオブジェクトのフィールドの多くはスロットと呼ばれ、Python C-API関数がこのタイプのオブジェクトで呼び出されたときに実行される関数(または関連する関数を示す構造)を示します。 そして、
PyNumber_Subtract
は異なる型の引数で動作するように見えますが、実際には、オペランドの型は逆参照され、この型に固有の減算関数が呼び出されます。 したがって、C-API関数は汎用的ではありません。 タイプと詳細からの抽象に依存しており、どのデータでも動作するようです(
TypeError
例外のスローも動作します)。
詳細を見てみましょう。
PyNumber_Subtract
は、2つの引数
./Objects/abstract.c
の汎用関数を呼び出します
nb_subtract
スロットで作業する必要があることを示します(他の操作にも同様のスロットがあります。たとえば、
nb_negative
を無効にする
sq_length
またはシーケンスの長さを決定する
sq_length
)。
binary_op
は、すべての作業を行う関数
binary_op1
でエラーチェックを行うラッパーです。
./Objects/abstract.c
(この関数のコードを読む-多くのことに目を向ける)は、
BINARY_SUBTRACT
操作のオペランドを
v
および
w
として受け入れ、数値を実装する関数へのポインターを含む構造体である
v->ob_type->tp_as_number
を逆参照しようとしますプロトコル。
binary_op1
は、
tp_as_number->nb_subtract
で、オペランドが減分され減算されたものとして互換性がないと判断した場合、減算を実行するか、特別な
Py_NotImplemented
値を返すC関数を見つけることを期待します(これは
TypeError
例外をスローします)。
オブジェクトの動作を変更したい場合は、
PyTypeObject
構造体をオーバーライドし、希望する方法でスロットを埋めるC拡張機能を記述できます。 Pythonで新しい型を作成するとき(
>>> class Foo(list): pass
create a new type 、クラスと型は同じものです)、構造を手動で記述せず、スロットを埋めません。 しかし、なぜこれらの型は組み込みのものと同じように振る舞うのですか? そうです、継承のために、型付けが重要な役割を果たします。 Pythonには、
list
や
dict
などの組み込み型が既にあります。 前述のように、これらのタイプには、対応するスロットを埋める特定の機能があります。これにより、オブジェクトに望ましい動作が与えられます。たとえば、値のシーケンスの可変性や値へのキーのマッピングです。 Pythonで(他のオブジェクトと同様に)ヒープ上に新しい型を作成すると、新しいC構造が動的に決定され、そのスロットは継承された基本的な型に従って埋められます( 複数の継承についてはどうですか? 他のエピソードで )。 なぜなら スロットがコピーされ、新しく認識されたサブタイプとベーシックの機能はほぼ同じです。 Pythonには、機能のない基本タイプ(
object
(Cの
PyBaseObject_Type
))があり、ほとんどすべてのスロットがゼロになり、何も継承せずに拡張できます。
したがって、Pythonで型を作成することはできず、常に他の何かから継承します(明示的な継承なしでクラスを定義すると、
object
から暗黙的に継承され
object
。Python2.xでは、この場合「クラシック」クラスが作成されます、それらは考慮されません)。 当然、常にすべてを継承する必要はありません。 上記のスニペットに示すように、Pythonで直接作成された型の動作を変更できます。
Bar
クラスで特別な __call__ メソッドを定義することにより 、このクラスのインスタンスを呼び出し可能にしました。
クラスの作成中のどこかで、この
__call__
メソッドに気づき、それを
tp_call
スロットに関連付けます。
./Objects/typeobject.c
複雑で重要な機能-これがすべて起こる場所です。 この関数の詳細については次の投稿で説明します。新しいタイプが作成された後、戻る前に、ほとんど最後の行に注目します
fixup_slot_dispatchers(type);
。 この関数は、新しいタイプで定義されたすべての正しい名前のメソッドを実行し、メソッドの名前に基づいて、タイプ構造内の必要なスロットに関連付けます(ただし、これらのメソッドはどこに格納されますか?)。
別の不可解な点:型を作成した後の型の
__call__
メソッドの定義は、メソッド定義の前にインスタンス化された場合でも、この型のインスタンスをどのように呼び出し可能にしますか? 簡単でシンプルな、私の友人。 覚えているように、タイプはオブジェクトであり、タイプは
type
(頭痛がある場合は、以下を実行してください:
>>> class Foo(list): pass ; type(Foo)
)。 したがって、クラスで何かをするとき(classの代わりにtypeという単語を書くこともできますが、別のコンテキストで「type」を使用するため、しばらくの間typeをクラスと呼びましょう)、たとえば、call、subtractまたは、属性を定義すると、
ob_type
オブジェクトの
ob_type
フィールドが逆参照され、クラス
type
が
type
あることが検出されます。 次に、
type->tp_setattro
スロットを使用して属性を設定します。 つまり、クラスには個別の属性設定機能を持たせることができます。 そして、そのような特定の関数(facebookでフリーズしたい場合、ここにそのページがあります
type_setattro
)は、
fixup_slot_dispatchers
が新しい属性を決定した後にすべての問題を解決するために使用する同じ関数(
update_one_slot
)を
fixup_slot_dispatchers
ます。 新しい詳細が明らかになりました!
これはおそらくPythonオブジェクトの紹介を終了する場所です。 旅行を楽しんでくれて、まだ私と一緒にいることを願っています。 この投稿を書くのは思ったよりもはるかに困難であったことを認めなければなりません(そして、
#python-dev
で夜遅くにAntoine PitrouとMark Dickinsの助けがなければ、おそらくあきらめます!)。 まだ多くの興味深いことが残っています。バイナリ演算で使用されるオペランドスロットはどれですか。 多重継承ではどうなりますか?また、それに関連付けられた
__slots__
と弱いリンク ? 埋め込みオブジェクトで何が起こっていますか? 辞書 、 リスト 、 セット、およびそれらのいとこはどのように機能しますか? そして最後に、 この奇跡はどうですか?
>>> a = object() >>> class C(object): pass ... >>> b = C() >>> a.foo = 5 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'object' object has no attribute 'foo' >>> b.foo = 5 >>>
object
を継承する
C
インスタンス
b
に任意の属性を追加するだけで、同じ
object
インスタンス
a
で同じことができないのはどうして?
b
は
__dict__
がありますが
a
はありません。 はい、そうです。 しかし、この新しい(そして完全に非自明な!)機能は、継承しない場合、どこから来たのでしょうか?
ハ! 私はそのような質問に非常に満足しています! 回答はになりますが、次のエピソードでは。
好奇心の強い人のための短い文献リスト:
- データモデルに関するドキュメント(力の供給側);
- 抽象的で特定のオブジェクト(電源側)に関するC-APIドキュメント。
- descrintro 、またはPython 2.2での型とクラスの統合は、長くて頭が長く、非常に重要な考古学的発見です(イースターエッグとしてインタープリターに追加する必要があると思います。
>>> import THAT
をお勧めします)。 - しかし、まずこのファイルは
./Objects/typeobject.c
です。 涙が出てベッドに倒れるまで何度も読んでください。
いい夢を。
覚えて! 興味のある人と会えるの はいつでも嬉しいです。