オブジェクトのライフサイクル¶
この節では、オブジェクトの生存期間を通じて型の各スロットがどのように関係するかを説明します。 各スロットの完全なリファレンスを意図したものではありません。 個々のスロットの詳細は 型オブジェクトの構造体 の該当する説明を参照してください。
生存期間中のイベント¶
次の図は、オブジェクトの生存期間中に起こり得るイベントの順序を示しています。 A から B への矢印は、イベント A の後にイベント B が起こり得ることを示します。 矢印のラベルは、A の後に B が起こるために必要な条件を表します。
説明:
型を呼び出して新しいオブジェクトを構築するとき:
tp_initが完了すると、オブジェクトを使えるようになります。オブジェクトへの最後の参照が取り除かれてからしばらく後:
オブジェクトに ファイナライズ済み の印が付いていない場合、ファイナライズ済み として印を付けて
tp_finalize関数を呼び出すことにより、ファイナライズされる可能性があります。 Python は最後の参照が削除されたときにオブジェクトをファイナライズするわけでは ありません。tp_finalizeが必ず呼び出されるようにするには、PyObject_CallFinalizerFromDealloc()を使ってください。オブジェクトにファイナライズ済みの印が付いている場合、ガベージコレクタが
tp_clearを呼び出して、オブジェクトの保持する参照をクリアする可能性があります。 オブジェクトの参照カウントがゼロになったときに呼び出されるわけでは ありません。オブジェクトを破棄するために
tp_deallocが呼び出されます。 コードの重複を避けるため、tp_deallocは通常tp_clearを呼び出してオブジェクトの参照を解放します。tp_deallocはオブジェクトの破棄を終えると、tp_freeを直接呼び出してメモリを解放します。 このスロットには通常、型に応じてPyObject_Free()またはPyObject_GC_Del()が自動的に設定されます。
tp_finalize関数は、必要に応じてオブジェクトへの参照を追加できます。 参照を追加するとオブジェクトが 復活 し、予定されていた破棄が阻止されます。 オブジェクトを復活させられるのはtp_finalizeだけです。tp_clearとtp_deallocは、tp_finalizeを呼び出さずに復活させることはできません。 復活したオブジェクトの ファイナライズ済み の印は、取り除かれる場合も残る場合もあります。 現在の Python は、ガベージコレクションをサポートするオブジェクト (Py_TPFLAGS_HAVE_GCフラグが設定されているもの) が復活した場合は ファイナライズ済み の印を残し、サポートしない場合は印を取り除きます。 これらの動作の一方または両方は、将来変更される可能性があります。tp_deallocは、オブジェクトの破棄処理にコードを再利用したい場合、PyObject_CallFinalizerFromDealloc()を介してtp_finalizeを呼び出すことができます。 これによって破棄前に必ずtp_finalizeが呼び出されるので、この方法を推奨します。 コード例はtp_deallocの説明を参照してください。オブジェクトが 孤立した循環参照群 の一部であり、
tp_clearが循環参照を解消できないか、その循環参照群が検出されない場合、オブジェクトはいつまでも回収できずに残ります (「リーク」します)。 検出されない原因としては、gc.disable()が呼び出された場合や、関連する型のいずれかで誤ってPy_TPFLAGS_HAVE_GCフラグが省かれた場合などが考えられます。gc.garbageを参照してください。
オブジェクトがガベージコレクションをサポートすると示されている場合 (tp_flags に Py_TPFLAGS_HAVE_GC フラグが設定されている場合)、次のイベントも起こり得ます。
ガベージコレクタは、ときどき
tp_traverseを呼び出して 孤立した循環参照群 を特定します。ガベージコレクタは 孤立した循環参照群 を発見すると、その群のオブジェクトの 1 つに ファイナライズ済み の印を付け、
tp_finalize関数があれば呼び出して、ファイナライズします。 これを、孤立した循環参照群がなくなるか、すべてのオブジェクトがファイナライズされるまで繰り返します。tp_finalizeは、孤立した循環参照群 の外から参照を追加することで、オブジェクトを復活させられます。 新しい参照が加わると、その群は孤立した循環参照群ではなくなります (循環参照が残っていても、オブジェクトはもはや孤立していません)。ガベージコレクタが 孤立した循環参照群 を発見し、その群のすべてのオブジェクトに ファイナライズ済み の印が付いている場合、 まだクリアされていないオブジェクトのうち 1 つ以上について、それぞれの
tp_clear関数を呼び出してクリアします (並行して行われる場合もあります)。 孤立した循環参照群が残っており、まだすべてのオブジェクトがクリアされていない間、この処理を繰り返します。
孤立した循環参照群の破棄¶
以下では、各オブジェクトをファイナライズまたはクリアした後も残り続けると仮定した 孤立した循環参照群 の段階を示します。
孤立した循環参照群がすべての段階を通過するなら、それはメモリリークです。
遅くともすべてのオブジェクトをクリアした時点で、孤立した循環参照群はなくなるべきです。
循環参照が解消されるか、ファイナライザによる復活によってオブジェクトが孤立しなくなると、孤立した循環参照群はなくなります (tp_finalize を参照)。
到達可能 (まだ孤立した循環参照群ではありません): すべてのオブジェクトが通常の到達可能な状態にあります。 循環参照があっても、外部からの参照があるのでまだ孤立していません。
到達不能だが整合性は保たれている: 循環参照しているオブジェクト群への外部からの最後の参照が取り除かれ、オブジェクトが孤立した状態です (これによって孤立した循環参照群が生じます)。 群のどのオブジェクトも、まだファイナライズもクリアもされていません。 孤立した循環参照群は、将来ガベージコレクタが実行されるまでこの段階にとどまります (次の実行で全オブジェクトを調べるとは限らないため、必ずしも次回とは限りません)。
ファイナライズ済みと未ファイナライズが混在: 孤立した循環参照群のオブジェクトは 1 つずつファイナライズされるため、ファイナライズ済みとそうでないオブジェクトが混在する期間があります。 ファイナライズの順序は規定されていないため、無作為に見えることがあります。 ファイナライズ済みオブジェクトは、未ファイナライズのオブジェクトから操作されても適切に動作しなければなりません。 また、未ファイナライズのオブジェクトは、自分の参照先の任意の一部がファイナライズされても問題なく扱える必要があります。
すべてファイナライズ済み: 孤立した循環参照群では、どのオブジェクトもクリアされる前に、すべてのオブジェクトがファイナライズされます。
ファイナライズ済みとクリア済みが混在: オブジェクトは逐次的にも並行的にもクリアされ得ますが、GIL を保持して行われます。 いずれの場合も、他のオブジェクトより先にクリアが完了するものがあります。 ファイナライズ済みオブジェクトは、自分の参照先の一部がクリアされても問題なく扱える必要があります。 PEP 442 ではこの段階を「cyclic trash」と呼んでいます。
リーク: 群のすべてのオブジェクトをファイナライズし、クリアした後も孤立した循環参照群が残る場合、それらのオブジェクトはいつまでも回収できずに残ります (
gc.garbageを参照)。 孤立した循環参照群がこの段階に達するのはバグです。 関与するオブジェクトのtp_clearメソッドが、必要な循環参照の解消に失敗したことを意味します。
tp_clear がなければ、Python には循環参照を安全に解消する方法がありません。
孤立した循環参照群のオブジェクトを単に破棄すると、ダングリングポインタが生じます。
破棄済みのオブジェクトを参照するオブジェクトが破棄される際に、未定義の動作を引き起こします。
クリアの段階を設けることで、オブジェクトの破棄は 2 段階になります。
まず tp_clear を呼び出し、オブジェクト同士の結び付きを解くのに十分なだけ部分的に破棄します。
次に tp_dealloc を呼び出し、破棄を完了します。
クリアとは異なり、ファイナライズは破棄の一段階ではありません。
ファイナライズ済みのオブジェクトも、設計上の契約を引き続き満たし、正しく動作しなければなりません。
オブジェクトのファイナライザは任意の Python コードを実行でき、参照を追加して、間近に迫った破棄を防ぐことさえできます。
ファイナライザと破棄が関係するのは呼び出し順序だけです。
ファイナライザが実行されるなら、それは破棄より前です。
破棄は tp_clear (呼び出す場合) から始まり、tp_dealloc で完了します。
孤立した循環参照群のオブジェクトを安全に回収するために、ファイナライズの段階は必須ではありません。 ただし、この段階があることで、オブジェクトがクリアされても適切に動作する型を設計しやすくなります。 オブジェクトのクリアでは、部分的に破棄された壊れた状態を避けられない場合があります。 クリア済みのオブジェクトのメソッド呼び出しや属性へのアクセスは、どれも安全でないかもしれません。 ファイナライズの段階があれば、クリア済みのオブジェクトとやり取りする可能性があるのはファイナライズ済みのオブジェクトだけです。 未ファイナライズのオブジェクトがやり取りする相手は、未クリア (ただしファイナライズ済みの場合もあります) のオブジェクトだけであると保証されます。
起こり得る参照関係をまとめると、次のようになります。
未ファイナライズのオブジェクトは、未ファイナライズまたはファイナライズ済みのオブジェクトへの参照や、それらからの参照を持つことがあります。 クリア済みオブジェクトとの間には、そのような参照はありません。
ファイナライズ済みのオブジェクトは、未ファイナライズ、ファイナライズ済み、クリア済みのいずれのオブジェクトへの参照や、それらからの参照も持ち得ます。
クリア済みのオブジェクトは、ファイナライズ済みまたはクリア済みのオブジェクトへの参照や、それらからの参照を持つことがあります。 未ファイナライズのオブジェクトとの間には、そのような参照はありません。
循環参照がなければ、最後の参照が削除された時点でオブジェクトを単に破棄できます。
使われなくなったオブジェクトを安全に回収するために、ファイナライズやクリアの段階は必要ありません。
それでも、破棄前に tp_finalize と tp_clear を自動的に呼び出すことには利点があります。
孤立した循環参照群に属していたかどうかにかかわらず、すべてのオブジェクトが常に同じ一連のイベントを経るようにすると、型の設計が簡単になるためです。
現在の Python は、孤立した循環参照群の破棄に必要な場合にだけ tp_finalize と tp_clear を呼び出します。
これは将来のバージョンで変更される可能性があります。
関数¶
メモリの割り当てと解放については ヒープ上でのオブジェクトの割り当て を参照してください。
-
void PyObject_CallFinalizer(PyObject *op)¶
tp_finalizeの説明に従ってオブジェクトをファイナライズします。tp_finalizeを直接呼び出すのではなく、この関数かPyObject_CallFinalizerFromDealloc()を呼び出してください。 この関数はtp_finalizeの重複する呼び出しを省く場合があるためです。 現在は、型がガベージコレクションをサポートする場合 (Py_TPFLAGS_HAVE_GCフラグが設定されている場合) にだけ重複を省きますが、将来変更される可能性があります。Added in version 3.4.
-
int PyObject_CallFinalizerFromDealloc(PyObject *op)¶
- 次に属します: Stable ABI (バージョン 3.15 より).
PyObject_CallFinalizer()と同じですが、オブジェクトのデストラクタ (tp_dealloc) の冒頭で呼び出すための関数です。 オブジェクトへの参照が残っていてはいけません。 ファイナライザがオブジェクトを復活させた場合は -1 を返します。 この場合、これ以上破棄を進めてはいけません。 それ以外の場合は 0 を返し、通常どおり破棄を続行できます。Added in version 3.4.
参考
コード例は
tp_deallocを参照してください。