CPython 弱引用 C API 深入解析:Reference/Proxy 对象、回调机制与源码级实现
发布时间:2026/9/7 6:11:20 作者:尧图编辑部 阅读量:1,286

CPython 弱引用 C API 深入解析Reference/Proxy 对象、回调机制与源码级实现【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本篇围绕 CPython 官方 C API 文档 weakref.rst 展开系统讲解弱引用weak reference在 C 扩展层的全部接口类型检查、弱引用创建含回调、解引用、死亡检测、弱引用清理等并结合 Objects/weakrefobject.c 等源码揭示基本引用复用、回调触发、free-threaded 构建下的线程安全设计帮助 C 扩展开发者正确、安全地操作弱引用。一、弱引用的两种对象类型Python 把弱引用作为一等对象支持C API 文档原文Python supportsweak referencesas first-class objects。C 层直接实现了两种弱引用对象Reference 对象weakref.ReferenceType即weakref.ref的返回类型一个简单引用调用它如ref()会返回被引用对象的新强引用目标消亡后返回NoneProxy 对象weakref.ProxyType/weakref.CallableProxyType尽可能充当原对象的代理——属性访问、数值运算、序列/映射操作都会转发给被引用对象目标死亡时抛出ReferenceError。Python 层对应实现见 Lib/weakref.py 与 C 核心实现 Objects/weakrefobject.c公共 API 声明在 Include/weakrefobject.h。从源码结构看两者共享同一个基础结构体PyWeakReference定义于 Include/cpython/weakrefobject.h#L8-L41struct _PyWeakReference { PyObject_HEAD PyObject *wr_object; /* 被引用对象死亡后置为 Py_Nonestealth reference不增加其引用计数 */ PyObject *wr_callback; /* 目标对象被回收时调用的回调可为 NULL */ Py_hash_t hash; /* 被引用对象哈希的缓存-1 表示尚未计算 */ PyWeakReference *wr_prev, *wr_next; /* 目标对象上弱引用双向链表的指针 */ vectorcallfunc vectorcall; #ifdef Py_GIL_DISABLED PyMutex *weakrefs_lock; /* free-threaded 构建下的锁指针 */ #endif };三个关键设计点值得注意stealth reference隐形引用wr_object不增加目标对象的引用计数——这正是弱的体现。注释明确写道 Note that this is a stealth reference: wr_objects refcount is not incremented to reflect this pointer双向链表挂在目标对象上每个支持弱引用的对象内部维护一条以自身为头部的弱引用链表通过tp_weaklistoffset定位wr_prev/wr_next就是链表指针哈希缓存ReferenceType支持hash()首次计算后缓存在self-hash见 Objects/weakrefobject.c#L189-L213。类型对象_PyWeakref_RefType、_PyWeakref_ProxyType、_PyWeakref_CallableProxyType在 Include/weakrefobject.h#L11-L13 中声明tp_name分别为weakref.ReferenceType、weakref.ProxyType、weakref.CallableProxyType见 Objects/weakrefobject.c#L506-L924。二、类型检查函数组weakref.rst 列出了 4 个检查函数它们always succeeds始终成功不设异常API语义PyWeakref_Check(ob)ob是 reference 或 proxy 对象则返回非零PyWeakref_CheckRef(ob)ob是 reference 对象或其子类则返回非零PyWeakref_CheckRefExact(ob)ob恰是 reference 对象不是子类才返回非零PyWeakref_CheckProxy(ob)ob是 proxy 对象则返回非零从源码看这四个函数全部是宏直接内联判定类型代价极低Include/weakrefobject.h#L15-L23#define PyWeakref_CheckRef(op) PyObject_TypeCheck((op), _PyWeakref_RefType) #define PyWeakref_CheckRefExact(op) \ Py_IS_TYPE((op), _PyWeakref_RefType) #define PyWeakref_CheckProxy(op) \ (Py_IS_TYPE((op), _PyWeakref_ProxyType) \ || Py_IS_TYPE((op), _PyWeakref_CallableProxyType)) #define PyWeakref_Check(op) \ (PyWeakref_CheckRef(op) || PyWeakref_CheckProxy(op))注意PyWeakref_CheckProxy同时覆盖普通 proxy 和可调用 proxy 两个类型而PyWeakref_CheckRefExact使用Py_IS_TYPE精确类型比较所以子类实例如自定义class Ref(weakref.ReferenceType)会被判为 0。测试用例 Lib/test/test_capi/test_weakref.py#L28-L59 完整验证了上述四种函数对普通对象、weakref.ref、子类实例、weakref.proxy的判定矩阵。另一个相关 API 是文档中反复 seealso 的PyType_SUPPORTS_WEAKREFS(type)声明于 Include/cpython/objimpl.h#L82用于判断目标对象是否支持弱引用。其内部实现极简Include/internal/pycore_object.h#L868-L871static inline int _PyType_SUPPORTS_WEAKREFS(PyTypeObject *type) { return (type-tp_weaklistoffset ! 0); }即只要类型的tp_weaklistoffset槽位非零就在对象尾部预留了弱引用链表指针的空间。内置的list、dict、set、函数、自定义类等默认支持而int、str、tuple这类不支持__weakref__的类型则不支持对它们创建弱引用会触发TypeError。三、创建弱引用PyWeakref_NewRef 与 PyWeakref_NewProxy3.1 API 签名与规则两个构造函数签名一致PyObject* PyWeakref_NewRef(PyObject *ob, PyObject *callback)/PyObject* PyWeakref_NewProxy(PyObject *ob, PyObject *callback)文档给出的规则返回值总是新的引用strong reference 意义上的新引用计数但不保证创建新对象——可能返回已存在的基本引用对象第二个参数callback是可选回调被引用对象ob被垃圾回收时被调用接收单个参数——弱引用对象本身callback可以为可调用对象、None或NULL若ob不是弱引用支持对象weakly referenceable或callback既不可调用也不是None/NULL则抛出TypeError并返回NULL。文档的versionchanged:: next标注说明对不合法 callback 抛TypeError是本次开发周期的行为变更更早版本对此行为不同编写扩展时应按现行文档处理。实现入口get_or_create_weakref()Objects/weakrefobject.c#L410-L460把上述规则落实为代码static PyWeakReference * get_or_create_weakref(PyTypeObject *type, PyObject *obj, PyObject *callback) { if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(obj))) { PyErr_Format(PyExc_TypeError, cannot create weak reference to %s object, Py_TYPE(obj)-tp_name); return NULL; } if (callback Py_None) { callback NULL; /* None 与 NULL 等价处理 */ } if (callback ! NULL !PyCallable_Check(callback)) { PyErr_Format(PyExc_TypeError, callback must be callable or None, not %T, callback); return NULL; } /* ... 复用基本引用或新建并插入链表 ... */ }PyWeakref_NewRef与PyWeakref_NewProxy只是把该函数分别绑定到_PyWeakref_RefType与 proxy 类型Objects/weakrefobject.c#L926-L941PyObject * PyWeakref_NewProxy(PyObject *ob, PyObject *callback) { PyTypeObject *type _PyWeakref_ProxyType; if (PyCallable_Check(ob)) { type _PyWeakref_CallableProxyType; /* 被引用对象可调用时升级类型 */ } return (PyObject *)get_or_create_weakref(type, ob, callback); }3.2 细节一可调用目标自动使用 CallableProxyType一个容易忽视的实现细节PyWeakref_NewProxy在ob可调用时会选用_PyWeakref_CallableProxyType见上文代码因为只有它的tp_call槽位proxy_call已填充——这样proxy(func)得到的代理才能像原函数一样被直接调用。测试 Lib/test/test_capi/test_weakref.py#L111-L134 验证了这一点newproxy(func)返回weakref.CallableProxyType实例而普通对象返回weakref.ProxyType。3.3 细节二基本引用复用不保证创建新对象这句文档承诺对应源码中的基本引用复用机制try_reuse_basic_refObjects/weakrefobject.c#L329-L353每个目标对象至多保留一个无回调的精确 ref和一个无回调的 proxy二者固定占据链表头部再次用callbackNULL创建同类型弱引用时直接复用它们通过_Py_TryIncref原子加引用计数防止竞态。这既省内存也让get_basic_refsObjects/weakrefobject.c#L276-L298能 O(1) 定位这些规范条目。带回调的弱引用则总是新建并插入基本条目之后insert_weakrefObjects/weakrefobject.c#L374-L397。3.4 最小 C 扩展示例综合文档与源码C 扩展中创建带回调弱引用的完整模式如下/* 回调必须可调用且接收一个参数弱引用对象本身 */ static PyObject *on_target_gone(PyObject *weakref, PyObject *Py_UNUSED(ignored)) { printf(target gone\n); Py_RETURN_NONE; } PyObject * create_example_ref(PyObject *target, PyObject *callback) /* callback 可为 NULL/None */ { if (PyType_SUPPORTS_WEAKREFS(Py_TYPE(target)) 0) { return NULL; /* get_or_create_weakref 内部同样会抛 TypeError */ } return PyWeakref_NewRef(target, callback); /* 失败返回 NULL 且已抛 TypeError */ }失败路径必须检查返回值TypeError有两种来源目标不支持弱引用、callback 非法二者都会在 Objects/weakrefobject.c#L413-L427 中被设置。四、解引用与死亡检测4.1 PyWeakref_GetRef3.13 新增int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)文档规定的三态返回成功*pobj指向被引用对象的新强引用返回1弱引用已死*pobj置NULL返回0错误ref不是弱引用对象设置异常并返回-1。源码实现Objects/weakrefobject.c#L957-L972int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) { if (ref NULL) { *pobj NULL; PyErr_BadInternalCall(); return -1; } if (!PyWeakref_Check(ref)) { *pobj NULL; PyErr_SetString(PyExc_TypeError, expected a weakref); return -1; } *pobj _PyWeakref_GET_REF(ref); return (*pobj ! NULL); }注意ref NULL属于编程错误PyErr_BadInternalCall产生 SystemError而非普通参数错误——测试 Lib/test/test_capi/test_weakref.py#L61-L74 分别断言存活时(1, obj)、死亡后0、传入42抛TypeError、传入NULL抛SystemError。该函数受 limited API 版本门控仅在Py_LIMITED_API 0x030D0000即 3.13时可见Include/weakrefobject.h#L31-L33。4.2 PyWeakref_IsDead3.14 新增int PyWeakref_IsDead(PyObject *ref)返回1表示死亡0表示存活ref非弱引用对象时设错误返回-1。实现Objects/weakrefobject.c#L943-L955委托给内部宏_PyWeakref_IS_DEADInclude/internal/pycore_weakref.h#L99-L120先原子读取wr_object若已是Py_None表示已被清理直接判死否则检查目标引用计数是否为 0_is_dead。_is_dead的实现还记录了一个微妙问题Include/internal/pycore_weakref.h#L56-L70当弱引用目标处于触发 trashcan 机制的长回收链中时弱引用的清理可能被延迟引用计数已归零但wr_object尚未置Py_None因此必须额外检查Py_REFCNT(obj) 0注释引用了 issue gh-60806。4.3 已弃用的 PyWeakref_GetObject旧接口PyWeakref_GetObject(ref)在源码中被注释为 removed in 3.15, but kept for stable ABI compatibilityObjects/weakrefobject.c#L975-L989仅为稳定 ABI 兼容保留。与PyWeakref_GetRef的关键差异死亡时返回借用的Py_None而非NULL 返回码 0成功时返回借用引用Py_DECREF(obj); return obj; // borrowed reference调用者绝不能Py_DECREF结果——这在迁移代码时是典型的悬垂引用来源。新项目应一律使用PyWeakref_GetRef3.13返回新强引用语义无歧义。五、回调触发与弱引用清理5.1 PyObject_ClearWeakRefsvoid PyObject_ClearWeakRefs(PyObject *object)文档定义由PyTypeObject.tp_dealloc处理器调用PyTypeObject定义见 Include/object.h遍历object的全部弱引用并逐个尝试调用其回调直到所有回调都被尝试为止。源码实现Objects/weakrefobject.c#L1014-L1096分四步工程上相当讲究前置断言object非 NULL、类型支持弱引用、且Py_REFCNT(object) 0——不满足则触发PyErr_BadInternalCall说明扩展在错误的时机调用了它快速路径链表头为空直接返回先清除无回调的基本 ref/proxy它们永远位于链表头部见第三节的插入规则循环清除到头部不再是基本条目为止收集-回调两阶段先在一把条纹锁的临界区里把剩余带回调或子类型的弱引用逐个摘除、窃取其 callback并配对装入一个临时 tuple用_Py_TryIncref防止并发下弱引用对象自身被回收锁释放后再统一遍历 tuple 调用handle_callback。回调本身由handle_callback执行Objects/weakrefobject.c#L994-L1006static void handle_callback(PyWeakReference *ref, PyObject *callback) { PyObject *cbresult PyObject_CallOneArg(callback, (PyObject *)ref); if (cbresult NULL) { PyErr_FormatUnraisable(Exception ignored while calling weakref callback %R, callback); } else { Py_DECREF(cbresult); } }注意回调以弱引用对象本身作为唯一实参与文档 it should accept a single parameter, which will be the weak reference object itself 一致且回调内抛出的异常不会传播而是通过PyErr_FormatUnraisable打印 Exception ignored while calling weakref callback ...。源码注释还指出gcmodule.c的handle_weakrefs()中存在handle_callback的内联拷贝因为 GC 回收循环引用时也会触发同类通知。先摘除再调用两阶段设计的动机在_PyWeakref_ClearRef的注释中交代得很清楚Objects/weakrefobject.c#L121-L138回调的释放可能间接触发其他弱引用回调在 gc 运行途中执行任意 Python 代码是灾难因此必须先把链表清理干净、让回调延后到世界状态安全时再跑。5.2 PyUnstable_Object_ClearWeakRefsNoCallbacks3.13 新增void PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *object)文档场景带终结器__del__即 finalizer类型的tp_dealloc。这类对象的释放顺序是先调PyObject_ClearWeakRefs清引用并执行回调→ 执行 finalizer → 最后调本函数清除 finalizer 期间可能新建的弱引用且不调用它们的回调避免递归进入正在析构的对象。文档明确提示大多数情况下应使用PyObject_ClearWeakRefs而非本函数。实现非常薄Objects/weakrefobject.c#L1098-L1104若类型支持弱引用就调用_PyWeakref_ClearWeakRefsNoCallbacksObjects/weakrefobject.c#L1124-L1137后者在锁保护下逐个调用_PyWeakref_ClearRef——该函数只断开链表指针并把wr_object置Py_None保留 callback 不释放不执行留给弱引用对象自己的tp_dealloc去Py_XDECREF见 Objects/weakrefobject.c#L105-L118 的clear_weakref。函数名前缀PyUnstable_表明它是未承诺稳定的 API。六、free-threaded 构建下的线程安全设计Objects/weakrefobject.c#L10-L35 顶部有一段专门的线程安全设计注释与文档接口本身强相关需要保护的三块可变状态弱引用自身的字段wr_object、hash、wr_callback、被引用对象的链表头指针、弱引用链表本身哈希值用弱引用对象自身的 per-object lock 保护链表及目标对象相关操作则用按被引用对象地址取模的条纹锁WEAKREF_LIST_LOCK(obj)Include/internal/pycore_weakref.h#L18-L30保护条纹锁必须用_Py_LOCK_DONT_DETACH方式加锁以支持WeakValueDictionary的原子删除语义因此持锁期间禁止任何可能挂起的操作——这也是PyObject_ClearWeakRefs要先摘除再回调的原因之一GC 运行时 world is stoppedGC 清理弱引用可不加锁Objects/weakrefobject.c#L158-L168 的gc_clear注释亦印证。解引用核心_PyWeakref_GET_REF在 free-threaded 构建下的完整竞争处理见 Include/internal/pycore_weakref.h#L72-L97原子读wr_object→ 若为Py_None返回 NULL → 加目标对象的条纹锁 → 在锁内再次检查并用_Py_TryIncref尝试原子加引用double-check失败说明目标正在死亡。带 GIL 的常规构建中LOCK_WEAKREFS等宏退化为空操作Include/internal/pycore_weakref.h#L38-L44。此外ReferenceType的调用入口weakref_vectorcallObjects/weakrefobject.c#L171-L187就是ref()的底层调用弱引用对象等于解引用——目标存活时返回其强引用已死时返回None而repr对死引用渲染为weakref at 0x...; deadObjects/weakrefobject.c#L215-L238。七、对照测试用例验证行为C API 行为测试集中在 Lib/test/test_capi/test_weakref.py通过_testcapi与_testlimitedcapi两个 C 测试模块弱引用语义的整体测试在 Lib/test/test_weakref.py。其中PyWeakref_NewRef的回调测试值得对照阅读Lib/test/test_capi/test_weakref.py#L91-L108def test_pyweakref_newref(self): newref _testlimitedcapi.pyweakref_newref obj Object() wr newref(obj) self.assertIs(type(wr), weakref.ReferenceType) # PyWeakref_NewRef() handles None callback as NULL callback wr newref(obj, None) # None 被当作无回调 self.assertRaises(TypeError, newref, obj, 42) # 非可调用 callback 抛 TypeError log [] wr newref(obj, log.append) self.assertEqual(log, []) del obj self.assertEqual(log, [wr]) # 回调收到的是弱引用对象本身最后两行精确对应文档描述目标del后回调log.append被自动调用参数正是wr——即弱引用对象自己。八、API 速查与选型建议API引入版本用途备注PyWeakref_Check/CheckRef/CheckRefExact/CheckProxy既有类型判定均为宏始终成功PyWeakref_NewRef(ob, cb)既有创建 ref 弱引用cb 需可调用/None/NULL否则 TypeError可能复用基本引用PyWeakref_NewProxy(ob, cb)既有创建 proxy 弱引用目标可调用时返回CallableProxyTypePyWeakref_GetRef(ref, p)3.13取目标强引用1/0/-1 三态limited API 需 3.13PyWeakref_IsDead(ref)3.14判断弱引用是否死亡非弱引用对象返回 -1 并设 TypeErrorPyObject_ClearWeakRefs(obj)既有dealloc 中清理并执行回调要求Py_REFCNT(obj) 0PyUnstable_Object_ClearWeakRefsNoCallbacks(obj)3.13清理但不执行回调供带__del__类型的 dealloc 收尾使用PyWeakref_GetObject(ref)既有弃用旧式取目标借用引用死则Py_None源码注释3.15 移除、仅保留稳定 ABI 兼容选型结论C 扩展中读取弱引用目标统一用PyWeakref_GetRef3.13返回值语义清晰判断死亡用PyWeakref_IsDead3.14或在 GIL 构建中结合解引用失败判断自定义类型的tp_dealloc里按类型是否有 finalizer 分别选用两个 Clear 函数——这与 Include/object.h 中PyTypeObject的tp_dealloc契约以及 weakref.rst 的文档描述完全一致。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考