对象生命周期¶
本节解释了对象在其整个生命周期中,一个类型的槽位(slots)之间如何相互关联。它并非旨在作为槽位的完整规范参考;相反,有关特定槽位的详细信息,请参阅类型对象结构中的槽位特定文档。
生命事件¶
下图展示了一个对象在其生命周期中可能发生的事件顺序。从 A 到 B 的箭头表示事件 B 可以在事件 A 发生后发生,箭头的标签表示 A 发生后 B 发生的条件。
说明
通过调用对象的类型构造新对象时
tp_init
完成后,对象即可使用。在最后一个对对象的引用被移除一段时间后
如果一个对象没有被标记为 finalized,它可能会通过标记为 finalized 并调用其
tp_finalize
函数来终结。Python 不 会在对对象的最后一个引用被删除时终结对象;请使用PyObject_CallFinalizerFromDealloc()
来确保tp_finalize
总是被调用。如果对象被标记为已终结,垃圾回收器可能会调用
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
的情况下不能复活对象。)复活对象可能会或可能不会导致对象的 finalized 标记被移除。目前,如果复活的对象支持垃圾回收(即设置了Py_TPFLAGS_HAVE_GC
标志),Python 不会从复活的对象中移除 finalized 标记,但如果对象不支持垃圾回收,则会移除该标记;这两种行为中的一种或两种都可能在未来发生变化。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
来识别 循环隔离体。当垃圾回收器发现一个循环隔离体时,它会通过将其标记为 finalized 并调用其
tp_finalize
函数(如果存在)来终结组中的一个对象。这会重复进行,直到循环隔离体不存在或所有对象都已终结。tp_finalize
允许通过从 循环隔离体 外部添加引用来复活对象。新引用导致对象组不再形成循环隔离体(引用循环可能仍然存在,但如果存在,对象不再隔离)。当垃圾回收器发现一个循环隔离体,并且组中所有对象都已被标记为 finalized 时,垃圾回收器会通过调用组中一个或多个未清除对象的
tp_clear
函数来清除它们(可能并发进行)。只要循环隔离体仍然存在并且并非所有对象都已被清除,此过程就会重复。
循环隔离体销毁¶
下面列出了一个假设的循环隔离体的生命阶段,该隔离体在每个成员对象被终结或清除后仍然存在。如果一个循环隔离体经历所有这些阶段,它就会发生内存泄漏;一旦所有对象被清除,它就应该消失,如果不是更早的话。循环隔离体消失的原因可能是引用循环被打破,或者因为终结器复活导致对象不再隔离(参见tp_finalize
)。
可达(尚未成为循环隔离体):所有对象都处于其正常的、可达状态。可能存在引用循环,但外部引用意味着对象尚未被隔离。
不可达但一致: 对一组循环对象外部的最后一个引用已被移除,导致这些对象变得隔离(因此诞生了一个循环隔离体)。该组中的任何对象都尚未被终结或清除。循环隔离体将保持此阶段,直到垃圾回收器的未来某个运行(不一定是下一次运行,因为下一次运行可能不会扫描每个对象)。
已终结和未终结混合: 循环隔离体中的对象是逐个终结的,这意味着在一段时间内,循环隔离体由已终结和未终结对象混合组成。终结顺序未指定,因此它可能看起来是随机的。已终结对象在未终结对象与其交互时必须以合理的方式行为,并且未终结对象必须能够容忍其引用对象中任意子集的终结。
全部终结: 循环隔离体中的所有对象都在其中任何一个被清除之前被终结。
已终结和已清除混合: 对象可以串行或并发地清除(但要保持 GIL);无论哪种方式,有些会先完成。已终结对象必须能够容忍其引用对象子集的清除。PEP 442 将此阶段称为“循环垃圾”。
泄漏: 如果一个循环隔离体在组中所有对象都被终结和清除后仍然存在,那么这些对象将无限期地无法回收(参见
gc.garbage
)。如果一个循环隔离体达到此阶段,则说明存在一个错误——这意味着参与对象的tp_clear
方法未能按要求打破引用循环。
如果tp_clear
不存在,那么 Python 将无法安全地打破引用循环。简单地销毁循环隔离体中的一个对象将导致悬空指针,当引用已销毁对象的另一个对象本身被销毁时,将触发未定义行为。清除步骤使对象销毁成为一个两阶段过程:首先调用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
中所述,终结对象。请调用此函数(或PyObject_CallFinalizerFromDealloc()
),而不是直接调用tp_finalize
,因为此函数可能会对tp_finalize
的多次调用进行去重。目前,只有在类型支持垃圾回收(即设置了Py_TPFLAGS_HAVE_GC
标志)的情况下才进行去重;这在未来可能会改变。
-
int PyObject_CallFinalizerFromDealloc(PyObject *op)¶
与
PyObject_CallFinalizer()
相同,但旨在在对象的析构函数 (tp_dealloc
) 开始时调用。不能有任何对对象的引用。如果对象的终结器复活了对象,此函数返回 -1;不应再进行销毁。否则,此函数返回 0,销毁可以正常继续。参见
tp_dealloc
的示例代码。