弱引用

16 min高级

Python弱引用详解:weakref模块、WeakKeyDictionary。

前置知识

学习目标

  • 掌握「1. 历史动机与发展脉络」的核心机制、典型用法与常见陷阱
  • 掌握「2. 形式化定义」的核心机制、典型用法与常见陷阱
  • 掌握「3. 理论推导与原理解析」的核心机制、典型用法与常见陷阱
  • 掌握「4. 代码示例」的核心机制、典型用法与常见陷阱
  • 掌握「5. 对比分析」的核心机制、典型用法与常见陷阱

1. 历史动机与发展脉络

弱引用的提出源于编程语言对”对象生命周期管理”的长期探索,其本质是区分”知道对象存在”与”保持对象存活”两种语义。

1.1 内存管理的理论起源

  • 1960:John McCarthy 在 Lisp 中实现首个垃圾回收器,采用标记-清除算法;
  • 1962:Harold MacLean 在 IBM 7090 上实现引用计数;
  • 1965:Edsger Dijkstra 提出”半自动内存管理”概念,区分强引用与弱引用;
  • 1976:Barry Liskov 在 CLU 语言中首次明确”弱指针”(weak pointer)概念;
  • 1983:Smalltalk-80 引入 WeakArray,成为弱引用容器的鼻祖;
  • 1990s:Java 引入 WeakReference、SoftReference、PhantomReference 三层引用体系;
  • 1990s:C++ 标准库引入 weak_ptr 配合 shared_ptr。

1.2 Python weakref 演进时间线

时间版本重要变化
1994Python 1.0仅有 sys.getrefcount,无弱引用支持
2001Python 2.1PEP 205 正式引入 weakref 模块
2003Python 2.3新式类默认支持弱引用;WeakKeyDictionary、WeakValueDictionary 稳定
2008Python 2.6weakref.proxy 增加 callback 参数
2010Python 2.7WeakSet 加入标准库
2014Python 3.4PEP 442 安全对象 finalizer,弱引用回调时机更可预测
2018Python 3.7weakref.finalize 加入 atexit=True 选项
2021Python 3.10weakref.ref 支持 __class_getitem__,类型注解友好
2023Python 3.12WeakSet 支持 typing.MutableSet 协议;性能优化
2025Python 3.13+弱引用在 free-threaded 构建(PEP 703)中的语义调整

1.3 PEP 205 的设计目标

PEP 205(Weak References in Python,由 Fred L. Drake, Jr. 与 Neil Schemenauer 于 2001 年提出)明确了弱引用的设计目标:

  1. 不增加引用计数:弱引用对对象”零”持有,不阻止 GC;
  2. 类型约束最小化:除少数内置不可变类型(int、str、tuple、list、dict)外,自定义类默认支持;
  3. 可扩展容器:提供 WeakKeyDictionary、WeakValueDictionary、WeakSet 三类标准容器;
  4. 回调机制:对象被回收时可触发回调,便于资源清理;
  5. 代理对象:proxy 提供透明访问,简化调用语法。

1.4 与其他语言的对比

语言弱引用类型主要用途
JavaWeakReference、SoftReference、PhantomReference缓存、Finalization
C++std::weak_ptr配合 shared_ptr 解决循环引用
C#WeakReference<T>、WeakReference缓存
Goruntime.KeepAlive + finalizer资源清理
RustWeak<T>配合 Rc/Arc 解决循环引用
JavaScriptWeakMap、WeakSet元数据附加、对象追踪
Swiftweak、unowned闭包与代理模式
Pythonweakref.ref、Weak*缓存、观察者、单例

2. 形式化定义

2.1 对象图的代数定义

设 OO 为对象集合,R⊆O×OR \subseteq O \times O 为引用关系。对象 oo 的引用集合定义为:

Ref(o)={o′∈O∣(o,o′)∈R}\text{Ref}(o) = \{ o' \in O \mid (o, o') \in R \}

强引用计数:

refcount(o)=∣{o′∈O∣o′→o 是强引用}∣\text{refcount}(o) = | \{ o' \in O \mid o' \to o \text{ 是强引用} \} |

可达性:从根集 G⊆OG \subseteq O(栈变量、全局变量)出发,强可达对象集合定义为:

Reach(G)=μX.(G∪{o∈O∣∃o′∈X,o′→o 是强引用})\text{Reach}(G) = \mu X . \left( G \cup \{ o \in O \mid \exists o' \in X, o' \to o \text{ 是强引用} \} \right)

其中 μ\mu 为最小不动点算子。

垃圾对象:o∈O∖Reach(G)o \in O \setminus \text{Reach}(G) 且 refcount(o)=0\text{refcount}(o) = 0(仅考虑强引用)。

2.2 弱引用的形式化

弱引用是一类特殊引用,不参与可达性分析:

WeakRef(o):={r∣r 指向 o,r∉强引用}\text{WeakRef}(o) := \{ r \mid r \text{ 指向 } o, r \notin \text{强引用} \}

形式化地,弱引用 rr 满足:

∀o∈O,refcount(o) 不含 r\forall o \in O, \text{refcount}(o) \text{ 不含 } r

当对象 oo 的所有强引用消失(refcount(o)=0\text{refcount}(o) = 0)时,oo 被 GC 回收,所有弱引用 rr 自动变为”失效”(dereferenced),r()r() 返回 None。

2.3 弱引用的有效性条件

Python 对象支持弱引用的充要条件:

SupportsWeakRef(o)  ⟺  type(o)∈WeakRefTypes\text{SupportsWeakRef}(o) \iff \text{type}(o) \in \text{WeakRefTypes}

其中:

  • WeakRefTypes\text{WeakRefTypes} 包括:用户自定义类、函数、方法(bound/unbound)、类型对象、abstractmethod、bytearray、memoryview、array.array、socket 等;
  • 非 WeakRefTypes\text{WeakRefTypes} 包括:int、float、complex、str、bytes、tuple、frozenset、list、dict、set、bool、NoneType、NotImplementedType、ellipsis 等内置类型实例。

可通过子类化添加弱引用支持:

class WeakList(list):
    """支持弱引用的列表子类。"""
    __slots__ = ("__weakref__",)  # 显式声明 weakref 槽位

wl = WeakList([1, 2, 3])
ref = weakref.ref(wl)  # OK

2.4 弱引用回调的时序

当对象 oo 被回收时,弱引用回调按以下顺序执行:

GC(o)→ClearWeakRefs(o)→InvokeCallbacks(o)→Deallocate(o)\text{GC}(o) \to \text{ClearWeakRefs}(o) \to \text{InvokeCallbacks}(o) \to \text{Deallocate}(o)

形式化地:

ClearWeakRefs(o):=∀r∈WeakRef(o),r.target:=None\text{ClearWeakRefs}(o) := \forall r \in \text{WeakRef}(o), r.\text{target} := \text{None} InvokeCallbacks(o):=∀r∈WeakRef(o) with callback f,f(r)\text{InvokeCallbacks}(o) := \forall r \in \text{WeakRef}(o) \text{ with callback } f, f(r)

关键性质:回调执行时对象 oo 已部分析构,不可在回调中重新访问 oo(会得到 None 或已失效的对象)。

2.5 Weak* 容器的语义

容器键值回收触发条件
WeakKeyDictionary弱引用强引用键对象被回收
WeakValueDictionary强引用弱引用值对象被回收
WeakSet元素(弱引用)-元素被回收

形式化地,WeakKeyDictionary 满足:

WKD[k]=v  ⟺  k∈Keys(WKD)∧refcount(k)>0\text{WKD}[k] = v \iff k \in \text{Keys}(\text{WKD}) \land \text{refcount}(k) > 0

当 refcount(k)→0\text{refcount}(k) \to 0,GC 回收 kk,触发弱键回调,从字典中删除 (k,v)(k, v) 条目。

3. 理论推导与原理解析

3.1 引用计数模型

CPython 默认使用引用计数(reference counting)作为主要 GC 策略。每个对象 oo 维护一个引用计数 refcount(o)\text{refcount}(o):

refcount(o)=∣Ref(o)∣\text{refcount}(o) = |\text{Ref}(o)|

引用计数变更时机:

事件变化
赋值 a = objrefcount(obj)+=1\text{refcount}(obj) += 1
del a 或 a 离开作用域refcount(obj)−=1\text{refcount}(obj) -= 1
加入容器 lst.append(obj)refcount(obj)+=1\text{refcount}(obj) += 1
从容器移除 lst.remove(obj)refcount(obj)−=1\text{refcount}(obj) -= 1
函数调用 f(obj)refcount(obj)+=1\text{refcount}(obj) += 1
函数返回refcount(obj)−=1\text{refcount}(obj) -= 1

当 refcount(o)=0\text{refcount}(o) = 0 时,对象立即被回收。

3.2 循环引用问题

引用计数无法处理循环引用:

a→b,b→a  ⟹  refcount(a)=1,refcount(b)=1a \to b, b \to a \implies \text{refcount}(a) = 1, \text{refcount}(b) = 1

但 a,ba, b 实际上已不可达。Python 通过 分代标记-清除 GC 处理:

  1. 周期性扫描容器对象,构建对象图;
  2. 从根集出发标记可达对象;
  3. 清除不可达的容器对象。

弱引用的作用:将循环引用中的一边改为弱引用,破坏循环:

class Parent:
    def __init__(self):
        self.children = []  # 强引用子对象

class Child:
    def __init__(self, parent):
        self._parent_ref = weakref.ref(parent)  # 弱引用父对象
        parent.children.append(self)

此时 refcount(parent)\text{refcount}(\text{parent}) 不含 child 的引用,parent 可被正常回收。

3.3 三代分代 GC 的数学模型

CPython 分代 GC 将对象分为三代:

Generation={G0,G1,G2}\text{Generation} = \{ G_0, G_1, G_2 \}

阈值(threshold):

Threshold=(700,10,10)\text{Threshold} = (700, 10, 10)

当某代分配数 - 释放数 ≥Thresholdi\geq \text{Threshold}_i 时,触发该代 GC。

trigger_gc(i):=(alloci−freei)≥Thresholdi\text{trigger\_gc}(i) := (\text{alloc}_i - \text{free}_i) \geq \text{Threshold}_i

弱引用与 GC 的协作:

  1. GC 在回收对象前,先清除所有指向该对象的弱引用;
  2. 调用弱引用的回调函数(如果设置了);
  3. 实际释放对象内存。

3.4 弱引用与线程安全

CPython 的 GIL 保证了字节码级别的原子性,但弱引用操作仍需注意:

  • ref() 调用与对象回收之间存在时间窗口,回调可能延迟;
  • WeakKeyDictionary 内部使用锁保护,但跨线程访问仍可能产生竞态;
  • 在 free-threaded 模式(PEP 703)下,弱引用语义可能调整。

3.5 弱引用的内存开销

每个弱引用对象占用约 64 字节(CPython 3.12 x86_64):

  • PyObject_HEAD:16 字节;
  • 目标指针:8 字节;
  • 回调指针:8 字节;
  • 链表节点(用于对象持有其所有弱引用):32 字节。

性能影响:

  • 创建弱引用:~100ns;
  • 调用 ref():~50ns;
  • 弱引用回调:~1μs。

4. 代码示例

4.1 基本弱引用

"""weakref 基础示例。Python 3.12+。"""
from __future__ import annotations

import weakref
import sys

class Resource:
    """可被弱引用的资源类。"""

    def __init__(self, name: str) -> None:
        self.name = name

    def __repr__(self) -> str:
        return f"Resource(name={self.name!r})"

def basic_weakref() -> None:
    """基本弱引用演示。"""
    resource = Resource("数据库连接")
    print(f"初始引用计数: {sys.getrefcount(resource)}")

    # 创建弱引用
    ref = weakref.ref(resource)
    print(f"创建弱引用后引用计数: {sys.getrefcount(resource)}")  # 不变

    # 通过 ref() 访问对象
    target = ref()
    if target is not None:
        print(f"通过弱引用访问: {target.name}")

    # 删除强引用后,弱引用失效
    del resource
    print(f"删除强引用后: {ref()}")  # None

if __name__ == "__main__":
    basic_weakref()

4.2 弱引用回调

"""弱引用回调示例。"""
from __future__ import annotations

import gc
import weakref

class TempFile:
    """临时文件,需在对象回收时清理。"""

    def __init__(self, path: str) -> None:
        self.path = path
        print(f"创建临时文件: {path}")

    def __repr__(self) -> str:
        return f"TempFile(path={self.path!r})"

def on_finalize(ref: weakref.ReferenceType) -> None:
    """对象被回收时的回调。

    Args:
        ref: 失效的弱引用对象(target 已为 None)。
    """
    print(f"弱引用回调触发: {ref} (target={ref()})")

def callback_demo() -> None:
    """弱引用回调演示。"""
    tmp = TempFile("/tmp/data.txt")
    ref = weakref.ref(tmp, on_finalize)

    print(f"对象存活: {ref()}")
    del tmp
    gc.collect()  # 强制 GC
    # 输出:弱引用回调触发: <weakref at 0x...; dead> (target=None)

if __name__ == "__main__":
    callback_demo()

4.3 WeakKeyDictionary

"""WeakKeyDictionary 示例:基于对象实例的元数据。"""
from __future__ import annotations

import weakref

class Node:
    """图节点。"""
    def __init__(self, name: str) -> None:
        self.name = name

    def __repr__(self) -> str:
        return f"Node({self.name!r})"

# 使用弱键字典存储节点元数据
metadata: weakref.WeakKeyDictionary[Node, dict] = weakref.WeakKeyDictionary()

def weak_key_dict_demo() -> None:
    """弱键字典演示。"""
    node1 = Node("A")
    node2 = Node("B")

    # 附加元数据
    metadata[node1] = {"visited": False, "weight": 1.0}
    metadata[node2] = {"visited": True, "weight": 2.0}

    print(f"node1 元数据: {metadata[node1]}")
    print(f"当前条目数: {len(metadata)}")  # 2

    # 删除 node1,对应条目自动清除
    del node1
    print(f"删除 node1 后条目数: {len(metadata)}")  # 1

    # 剩余条目
    for node, data in metadata.items():
        print(f"剩余: {node} -> {data}")

if __name__ == "__main__":
    weak_key_dict_demo()

4.4 WeakValueDictionary

"""WeakValueDictionary 示例:对象缓存。"""
from __future__ import annotations

import weakref
from typing import Callable

class ExpensiveObject:
    """昂贵的对象,需要缓存复用。"""

    _instances: weakref.WeakValueDictionary[str, "ExpensiveObject"] = (
        weakref.WeakValueDictionary()
    )

    def __init__(self, key: str) -> None:
        self.key = key
        self._data = self._compute(key)

    @staticmethod
    def _compute(key: str) -> dict:
        """模拟昂贵的计算。"""
        print(f"计算 {key}...")
        return {"result": f"computed_{key}"}

    @classmethod
    def get_or_create(cls, key: str) -> "ExpensiveObject":
        """获取或创建实例(缓存复用)。"""
        obj = cls._instances.get(key)
        if obj is None:
            obj = cls(key)
            cls._instances[key] = obj  # 缓存为弱引用
        return obj

def weak_value_dict_demo() -> None:
    """弱值字典演示。"""
    # 第一次创建(昂贵)
    obj1 = ExpensiveObject.get_or_create("query_1")
    print(f"obj1.key: {obj1.key}")

    # 第二次获取(从缓存)
    obj2 = ExpensiveObject.get_or_create("query_1")
    print(f"obj1 is obj2: {obj1 is obj2}")  # True

    # 删除所有强引用后,缓存自动清除
    del obj1, obj2
    print(f"缓存条目数: {len(ExpensiveObject._instances)}")  # 0

    # 再次创建(需重新计算)
    obj3 = ExpensiveObject.get_or_create("query_1")

if __name__ == "__main__":
    weak_value_dict_demo()

4.5 WeakSet

"""WeakSet 示例:观察者模式。"""
from __future__ import annotations

import weakref
from typing import Callable

class Observer:
    """观察者。"""
    def __init__(self, name: str) -> None:
        self.name = name

    def update(self, message: str) -> None:
        print(f"[{self.name}] 收到: {message}")

class Subject:
    """被观察者。"""

    def __init__(self) -> None:
        # 使用 WeakSet 自动清理失效的观察者
        self._observers: weakref.WeakSet[Observer] = weakref.WeakSet()

    def register(self, observer: Observer) -> None:
        """注册观察者。"""
        self._observers.add(observer)

    def unregister(self, observer: Observer) -> None:
        """注销观察者。"""
        self._observers.discard(observer)

    def notify(self, message: str) -> None:
        """通知所有观察者。"""
        for observer in self._observers:
            observer.update(message)

def weak_set_demo() -> None:
    """WeakSet 演示。"""
    subject = Subject()

    obs1 = Observer("观察者1")
    obs2 = Observer("观察者2")
    subject.register(obs1)
    subject.register(obs2)

    subject.notify("事件 A")

    # 删除 obs1,自动从观察者集合中移除
    del obs1
    subject.notify("事件 B")  # 只通知观察者2

if __name__ == "__main__":
    weak_set_demo()

4.6 弱引用代理(proxy)

"""弱引用代理示例。"""
from __future__ import annotations

import weakref

class Service:
    """远程服务客户端。"""
    def __init__(self, url: str) -> None:
        self.url = url

    def call(self, method: str) -> str:
        return f"调用 {self.url}/{method}"

class Client:
    """持有服务代理的客户端。"""
    def __init__(self, service: Service) -> None:
        # 使用 proxy 透明访问,避免强引用
        self._service = weakref.proxy(service)

    def invoke(self, method: str) -> str:
        # 直接调用,无需 ref()
        return self._service.call(method)

def proxy_demo() -> None:
    """代理演示。"""
    service = Service("https://api.example.com")
    client = Client(service)

    print(client.invoke("users"))  # 正常调用

    del service
    # 此时 client._service 已失效
    try:
        client.invoke("posts")
    except ReferenceError as e:
        print(f"代理已失效: {e}")

if __name__ == "__main__":
    proxy_demo()

4.7 finalize 自动资源清理

"""finalize 示例:替代 __del__ 的资源清理。"""
from __future__ import annotations

import os
import tempfile
import weakref
from pathlib import Path

class TempWorkspace:
    """临时工作空间,对象回收时自动清理目录。"""

    def __init__(self, prefix: str = "workspace_") -> None:
        # 创建临时目录
        self._tmpdir = tempfile.mkdtemp(prefix=prefix)
        self.path = Path(self._tmpdir)
        print(f"创建临时目录: {self.path}")

        # 注册 finalizer,对象回收时自动清理
        self._finalizer = weakref.finalize(
            self,
            TempWorkspace._cleanup,
            self.path,
        )

    @staticmethod
    def _cleanup(path: Path) -> None:
        """静态方法清理(避免引用 self)。"""
        import shutil
        if path.exists():
            shutil.rmtree(path)
            print(f"已清理临时目录: {path}")

    def close(self) -> None:
        """手动清理。"""
        self._finalizer()

    @property
    def is_closed(self) -> bool:
        return not self._finalizer.alive

def finalize_demo() -> None:
    """finalize 演示。"""
    ws = TempWorkspace()
    print(f"路径: {ws.path}")
    print(f"已关闭: {ws.is_closed}")  # False

    # 自动清理
    del ws
    # 输出:已清理临时目录: ...

    # 手动清理示例
    ws2 = TempWorkspace()
    ws2.close()
    print(f"ws2 已关闭: {ws2.is_closed}")  # True

if __name__ == "__main__":
    finalize_demo()

4.8 循环引用规避

"""循环引用规避示例:父子结构。"""
from __future__ import annotations

import gc
import weakref

class TreeNode:
    """树节点,使用弱引用避免父子循环。"""

    def __init__(self, name: str) -> None:
        self.name = name
        self.children: list[TreeNode] = []
        self._parent_ref: weakref.ReferenceType[TreeNode] | None = None

    @property
    def parent(self) -> "TreeNode | None":
        """通过弱引用访问父节点。"""
        if self._parent_ref is None:
            return None
        return self._parent_ref()

    @parent.setter
    def parent(self, value: "TreeNode | None") -> None:
        if value is None:
            self._parent_ref = None
        else:
            self._parent_ref = weakref.ref(value)
            value.children.append(self)

    def __repr__(self) -> str:
        return f"TreeNode({self.name!r})"

def cyclic_ref_demo() -> None:
    """循环引用规避演示。"""
    root = TreeNode("root")
    child = TreeNode("child")
    child.parent = root

    print(f"child.parent: {child.parent}")

    # 删除 root,无循环引用,可被立即回收
    del root
    print(f"child.parent (after del root): {child.parent}")  # None

    # 即使没有 GC 也能正常工作
    gc.disable()
    child2 = TreeNode("child2")
    parent = TreeNode("parent2")
    child2.parent = parent
    del parent
    print(f"child2.parent: {child2.parent}")  # None
    gc.enable()

if __name__ == "__main__":
    cyclic_ref_demo()

4.9 单例注册表

"""单例注册表示例。"""
from __future__ import annotations

import weakref
from typing import Callable, TypeVar

T = TypeVar("T")

class SingletonRegistry:
    """弱引用单例注册表。

    特点:
    - 同一 key 对应同一实例;
    - 当所有强引用消失时,单例自动移除;
    - 便于测试时清理状态。
    """

    _instances: weakref.WeakValueDictionary[str, object] = weakref.WeakValueDictionary()

    @classmethod
    def get_or_create(
        cls,
        key: str,
        factory: Callable[[], T],
    ) -> T:
        """获取或创建单例。

        Args:
            key: 单例标识。
            factory: 工厂函数,首次调用时创建实例。

        Returns:
            单例实例。
        """
        instance = cls._instances.get(key)
        if instance is None:
            instance = factory()
            cls._instances[key] = instance
        return instance  # type: ignore

    @classmethod
    def clear(cls, key: str | None = None) -> None:
        """清除单例。

        Args:
            key: 指定 key 清除;None 清除所有。
        """
        if key is None:
            cls._instances.clear()
        else:
            cls._instances.pop(key, None)

# 使用示例
class Database:
    def __init__(self, dsn: str):
        self.dsn = dsn
        print(f"连接数据库: {dsn}")

def singleton_demo() -> None:
    db1 = SingletonRegistry.get_or_create("db", lambda: Database("postgresql://localhost"))
    db2 = SingletonRegistry.get_or_create("db", lambda: Database("postgresql://localhost"))
    print(f"db1 is db2: {db1 is db2}")  # True

    del db1, db2
    print(f"单例数: {len(SingletonRegistry._instances)}")  # 0

    # 再次创建(需重新初始化)
    db3 = SingletonRegistry.get_or_create("db", lambda: Database("postgresql://localhost"))

if __name__ == "__main__":
    singleton_demo()

4.10 弱引用描述符

"""弱引用描述符示例:可观察属性。"""
from __future__ import annotations

import weakref
from typing import Callable

class ObservableAttribute:
    """可观察的属性描述符。

    特点:
    - 监听属性变化;
    - 观察者列表使用弱引用,避免内存泄漏;
    - 自动清理失效的观察者。
    """

    def __init__(self, default=None) -> None:
        self._default = default
        self._observers: weakref.WeakKeyDictionary = weakref.WeakKeyDictionary()
        self._storage_name: str = ""

    def __set_name__(self, owner: type, name: str) -> None:
        self._storage_name = f"_observable_{name}"

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self._storage_name, self._default)

    def __set__(self, obj, value) -> None:
        old = getattr(obj, self._storage_name, self._default)
        setattr(obj, self._storage_name, value)
        if old != value:
            self._notify(obj, old, value)

    def observe(self, obj, callback: Callable) -> None:
        """注册观察者。"""
        if obj not in self._observers:
            self._observers[obj] = []
        self._observers[obj].append(callback)

    def _notify(self, obj, old, new) -> None:
        callbacks = self._observers.get(obj, [])
        for cb in callbacks:
            cb(old, new)

class User:
    """使用可观察属性的用户类。"""
    name = ObservableAttribute(default="")
    age = ObservableAttribute(default=0)

    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age

def descriptor_demo() -> None:
    user = User("Alice", 30)

    # 注册观察者
    def on_name_change(old, new):
        print(f"姓名变化: {old!r} -> {new!r}")

    def on_age_change(old, new):
        print(f"年龄变化: {old!r} -> {new!r}")

    User.name.observe(user, on_name_change)
    User.age.observe(user, on_age_change)

    # 修改属性触发回调
    user.name = "Bob"  # 输出:姓名变化: 'Alice' -> 'Bob'
    user.age = 31      # 输出:年龄变化: 30 -> 31

if __name__ == "__main__":
    descriptor_demo()

5. 对比分析

5.1 WeakValueDictionary vs WeakKeyDictionary

维度WeakValueDictionaryWeakKeyDictionary
键强引用(普通对象)弱引用(必须支持弱引用)
值弱引用(必须支持弱引用)强引用(普通对象)
回收触发值对象被回收键对象被回收
典型用途缓存(按 key 存储对象)元数据(按对象附加信息)
内部实现键 + 弱值引用 + 回调弱键引用 + 值
性能略慢(需管理弱值)略快

5.2 weakref.ref vs weakref.proxy

维度refproxy
访问方式ref() 显式调用透明访问
空引用返回 None抛出 ReferenceError
性能~50ns~80ns(每次访问检查)
序列化支持不支持
类型检查isinstance(x, weakref.ref)isinstance(x, weakref.ProxyType)
适用场景需要明确控制访问需要透明代理

5.3 weakref.finalize vs del

维度weakref.finalize__del__
引用 self不引用(避免复活)引用 self
GC 影响不影响可能触发复活
跨解释器安全可能不安全
测试性可手动调用 finalizer()难以测试
优先级推荐使用不推荐

5.4 weakref vs Java WeakReference

维度Python weakrefJava WeakReference
引用层级单层(弱)三层(Weak、Soft、Phantom)
Soft 语义无(需自行实现)内置(内存不足时回收)
Phantom 语义无(finalize 替代)内置(finalization 后回收)
ReferenceQueue无有
性能较高中等(需 GC 协调)

5.5 weakref vs JavaScript WeakMap

维度Python weakrefJavaScript WeakMap
键类型任意支持弱引用的对象仅 object
值类型任意任意
迭代支持(WeakKeyDictionary)不支持
大小查询支持不支持
全局弱引用列表支持不支持
用途缓存、观察者、单例元数据附加

6. 常见陷阱与最佳实践

6.1 陷阱:内置类型不支持弱引用

# 错误:int 不支持弱引用
import weakref

# weakref.ref(42)  # TypeError: cannot create weak reference to 'int' object

# 解决方案 1:子类化
class WeakInt(int):
    pass

n = WeakInt(42)
ref = weakref.ref(n)
print(ref())  # 42

# 解决方案 2:包装为对象
class IntWrapper:
    def __init__(self, value: int):
        self.value = value

w = IntWrapper(42)
ref = weakref.ref(w)

6.2 陷阱:弱引用回调中访问对象

# 错误:回调中尝试访问已回收的对象
import weakref

class Resource:
    pass

def bad_callback(ref):
    obj = ref()  # None,对象已回收
    # obj.method()  # AttributeError: 'NoneType' object has no attribute 'method'

# 正确:在回调外部捕获所需信息
def good_callback(ref, name="resource"):
    print(f"{name} 已被回收")

resource = Resource()
ref = weakref.ref(resource, lambda r: good_callback(r, "my_resource"))
del resource

6.3 陷阱:slots 未声明 weakref

# 错误:__slots__ 未包含 __weakref__
class NoWeakRef:
    __slots__ = ("value",)

# weakref.ref(NoWeakRef())  # TypeError: cannot create weak reference to 'NoWeakRef' object

# 正确:显式声明 __weakref__
class WithWeakRef:
    __slots__ = ("value", "__weakref__")

obj = WithWeakRef()
obj.value = 42
ref = weakref.ref(obj)

6.4 陷阱:WeakKeyDictionary 的键必须可哈希

# 错误:列表不支持弱引用也不可哈希
import weakref

# metadata = weakref.WeakKeyDictionary()
# metadata[[1, 2, 3]] = "data"  # TypeError

# 正确:使用可哈希且支持弱引用的类型
class Key:
    def __init__(self, value):
        self.value = value
    def __hash__(self):
        return hash(self.value)
    def __eq__(self, other):
        return isinstance(other, Key) and self.value == other.value

metadata = weakref.WeakKeyDictionary()
k = Key("user_1")
metadata[k] = {"name": "Alice"}

6.5 陷阱:弱引用回调时序不可控

# 错误:依赖回调的执行时序
import weakref
import gc

class Task:
    pass

tasks_completed = []

def on_task_done(ref):
    tasks_completed.append(ref)

# 不同实现下,回调时序可能不同
task = Task()
ref = weakref.ref(task, on_task_done)
del task
gc.collect()  # 显式触发 GC
# tasks_completed 可能为空(GC 尚未执行)

6.6 陷阱:finalize 闭包捕获 self

# 错误:闭包捕获 self,导致对象无法回收
import weakref

class Bad:
    def __init__(self):
        # 闭包捕获 self,形成强引用
        self._finalizer = weakref.finalize(self, self._cleanup)
        # 此时 self 永远不会被回收!

    def _cleanup(self):
        print("清理")

# 正确:使用静态方法
class Good:
    def __init__(self, path):
        self._finalizer = weakref.finalize(self, Good._cleanup, path)

    @staticmethod
    def _cleanup(path):
        print(f"清理 {path}")

6.7 陷阱:跨线程使用弱引用容器

# 错误:跨线程并发访问 WeakKeyDictionary
import threading
import weakref

class Item:
    pass

data = weakref.WeakKeyDictionary()

def worker():
    item = Item()
    data[item] = "value"
    # ... 其他操作

# 多线程并发修改,可能导致内部状态不一致
threads = [threading.Thread(target=worker) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()

# 正确:使用锁保护
from threading import RLock

class SafeWeakDict:
    def __init__(self):
        self._data = weakref.WeakKeyDictionary()
        self._lock = RLock()

    def __setitem__(self, key, value):
        with self._lock:
            self._data[key] = value

    def __getitem__(self, key):
        with self._lock:
            return self._data[key]

6.8 最佳实践总结

  1. 优先使用 finalize 而非 del:更安全,可测试;
  2. 避免在回调中访问对象:仅使用闭包外部捕获的信息;
  3. 显式声明 weakref 槽位:使用 __slots__ 时;
  4. 跨线程加锁:访问弱引用容器时;
  5. 不依赖回调时序:弱引用回调可能延迟执行;
  6. 使用弱引用打破循环:在父子、观察者等结构中;
  7. 测试时强制 GC:gc.collect() 确保回调触发。

7. 工程实践

7.1 项目结构

flowchart TD
    T0["my_app/"]
    T1["pyproject.toml"]
    T2["src/"]
    T3["myapp/"]
    T4["__init__.py"]
    T5["cache.py          # 基于 WeakValueDictionary 的缓存"]
    T6["observer.py       # 基于 WeakSet 的观察者"]
    T7["resources.py      # 基于 finalize 的资源管理"]
    T8["descriptors.py    # 弱引用描述符"]
    T9["tests/"]
    T10["test_cache.py"]
    T0 --> T1
    T0 --> T2
    T8 --> T9
    T9 --> T10

7.2 pyproject.toml 配置

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "pytest-asyncio>=0.23",
    "ruff>=0.3",
    "mypy>=1.8",
]

[tool.ruff]
line-length = 100
target-version = "py312"

7.3 完整缓存实现

"""基于弱引用的多级缓存实现。Python 3.12+。"""
from __future__ import annotations

import threading
import time
import weakref
from collections import OrderedDict
from typing import Any, Callable

class TTLCache:
    """带 TTL 和 LRU 的弱引用缓存。

    特点:
    - 弱引用值:对象无强引用时自动清除;
    - TTL:到期自动清除;
    - LRU:超过 maxsize 时淘汰最久未访问;
    - 线程安全。
    """

    def __init__(
        self,
        maxsize: int = 128,
        ttl: float = 300.0,
    ) -> None:
        self.maxsize = maxsize
        self.ttl = ttl
        self._data: OrderedDict = OrderedDict()
        self._lock = threading.RLock()
        self._weak_refs: weakref.WeakValueDictionary = weakref.WeakValueDictionary()

    def get(self, key: str) -> Any | None:
        """获取缓存值。"""
        with self._lock:
            if key not in self._data:
                return None
            value, expire_at = self._data[key]
            if time.monotonic() >= expire_at:
                del self._data[key]
                return None
            self._data.move_to_end(key)
            return value

    def set(self, key: str, value: Any) -> None:
        """设置缓存值。"""
        with self._lock:
            self._data[key] = (value, time.monotonic() + self.ttl)
            self._data.move_to_end(key)
            if len(self._data) > self.maxsize:
                self._data.popitem(last=False)

    def get_or_create(
        self,
        key: str,
        factory: Callable[[], Any],
    ) -> Any:
        """获取或创建缓存值。"""
        value = self.get(key)
        if value is None:
            value = factory()
            self.set(key, value)
        return value

    def clear(self) -> None:
        """清空缓存。"""
        with self._lock:
            self._data.clear()

# 使用示例
if __name__ == "__main__":
    cache = TTLCache(maxsize=100, ttl=60)

    # 第一次调用(计算)
    result1 = cache.get_or_create("expensive_1", lambda: sum(range(1000000)))
    print(f"result1: {result1}")

    # 第二次调用(缓存命中)
    result2 = cache.get_or_create("expensive_1", lambda: sum(range(1000000)))
    print(f"result2: {result2}")
    print(f"same: {result1 is result2}")

7.4 观察者模式

"""弱引用观察者模式。Python 3.12+。"""
from __future__ import annotations

import weakref
from typing import Callable, Any

class EventBus:
    """事件总线,观察者使用弱引用。"""

    def __init__(self) -> None:
        self._handlers: weakref.WeakKeyDictionary = weakref.WeakKeyDictionary()

    def subscribe(self, handler: Callable[[Any], None]) -> None:
        """订阅事件。

        Args:
            handler: 事件处理函数(必须是可弱引用的对象)。
        """
        # 函数对象支持弱引用
        self._handlers[handler] = True

    def unsubscribe(self, handler: Callable[[Any], None]) -> None:
        """取消订阅。"""
        self._handlers.pop(handler, None)

    def publish(self, event: Any) -> None:
        """发布事件。"""
        # 复制一份,避免迭代时修改
        handlers = list(self._handlers.keys())
        for handler in handlers:
            try:
                handler(event)
            except Exception as e:
                print(f"事件处理器异常: {e}")

# 使用示例
def on_user_created(event):
    print(f"用户创建: {event}")

def on_user_deleted(event):
    print(f"用户删除: {event}")

if __name__ == "__main__":
    bus = EventBus()
    bus.subscribe(on_user_created)
    bus.subscribe(on_user_deleted)

    bus.publish({"type": "created", "user_id": 1})
    # 输出:用户创建: {'type': 'created', 'user_id': 1}
    # 输出:用户删除: {'type': 'created', 'user_id': 1}

    # 删除处理器函数对象,自动从订阅列表中移除
    del on_user_deleted
    bus.publish({"type": "created", "user_id": 2})
    # 仅输出:用户创建: ...

7.5 资源管理

"""基于 finalize 的资源管理。Python 3.12+。"""
from __future__ import annotations

import os
import tempfile
import weakref
from pathlib import Path
from typing import Callable

class ManagedResource:
    """受管资源,自动清理。"""

    def __init__(
        self,
        factory: Callable[[], Path],
        cleanup: Callable[[Path], None],
    ) -> None:
        self.path = factory()
        self._finalizer = weakref.finalize(self, cleanup, self.path)

    @property
    def is_closed(self) -> bool:
        return not self._finalizer.alive

    def close(self) -> None:
        """手动关闭。"""
        self._finalizer()

def create_temp_file(prefix: str = "tmp_") -> Path:
    """创建临时文件。"""
    fd, path = tempfile.mkstemp(prefix=prefix)
    os.close(fd)
    return Path(path)

def cleanup_file(path: Path) -> None:
    """清理临时文件。"""
    if path.exists():
        path.unlink()
        print(f"已清理: {path}")

# 使用示例
if __name__ == "__main__":
    resources = []

    # 创建多个临时文件
    for i in range(5):
        r = ManagedResource(create_temp_file, cleanup_file)
        r.path.write_text(f"内容 {i}")
        resources.append(r)
        print(f"创建: {r.path}")

    # 释放部分资源
    del resources[:3]

    # 剩余资源
    print(f"剩余: {len(resources)}")
    for r in resources:
        print(f"  {r.path}, closed: {r.is_closed}")

    # 手动关闭
    for r in resources:
        r.close()

    # GC 回收剩余的弱引用
    import gc
    gc.collect()

7.6 调试与测试

"""弱引用调试与测试示例。Python 3.12+。"""
from __future__ import annotations

import gc
import sys
import weakref

def test_weak_ref_basic():
    """测试弱引用基础功能。"""
    class Obj:
        pass

    obj = Obj()
    ref = weakref.ref(obj)
    assert ref() is obj

    del obj
    assert ref() is None

def test_weak_value_dict():
    """测试弱值字典。"""
    class Obj:
        pass

    cache = weakref.WeakValueDictionary()
    obj = Obj()
    cache["key"] = obj
    assert cache["key"] is obj

    del obj
    assert "key" not in cache

def test_finalize():
    """测试 finalize。"""
    import tempfile
    import os

    path = tempfile.mktemp()
    with open(path, "w") as f:
        f.write("test")

    class Resource:
        def __init__(self):
            self._finalizer = weakref.finalize(self, os.unlink, path)

    r = Resource()
    assert os.path.exists(path)

    del r
    gc.collect()
    assert not os.path.exists(path)

def test_callbacks():
    """测试弱引用回调。"""
    class Obj:
        pass

    called = []

    def callback(ref):
        called.append(ref)

    obj = Obj()
    ref = weakref.ref(obj, callback)

    del obj
    gc.collect()

    assert len(called) == 1
    assert called[0] is ref

if __name__ == "__main__":
    test_weak_ref_basic()
    test_weak_value_dict()
    test_finalize()
    test_callbacks()
    print("所有测试通过")

8. 案例研究

8.1 Python 标准库中的弱引用应用

functools.lru_cache

Python 标准库 functools.lru_cache 使用 WeakKeyDictionary 存储函数参数到结果的映射:

import functools

@functools.lru_cache(maxsize=128)
def expensive_computation(n):
    return sum(i * i for i in range(n))

weakref.WeakValueDictionary 在 __import__ 中

CPython 内部使用 WeakValueDictionary 缓存已导入的模块,当模块无强引用时自动清除。

unittest.mock 中的弱引用

unittest.mock 使用弱引用跟踪 mock 对象,便于自动清理。

8.2 Django 中的弱引用

Django 使用 WeakValueDictionary 缓存查询集:

# Django ORM 内部(简化)
class QuerySetCache:
    _cache = weakref.WeakValueDictionary()

    @classmethod
    def get(cls, key):
        return cls._cache.get(key)

    @classmethod
    def set(cls, key, value):
        cls._cache[key] = value

8.3 Flask 中的弱引用

Flask 使用 WeakKeyDictionary 跟踪应用上下文:

# Flask 内部(简化)
class AppContext:
    _apps = weakref.WeakKeyDictionary()

    def __init__(self, app):
        self.app = app
        AppContext._apps[app] = self

8.4 SQLAlchemy 中的弱引用

SQLAlchemy 使用 WeakIdentityMap 缓存 ORM 对象,避免内存泄漏:

# SQLAlchemy 内部(简化)
class IdentityMap:
    def __init__(self):
        self._map = weakref.WeakValueDictionary()

    def add(self, obj):
        key = (type(obj), obj.id)
        self._map[key] = obj

    def get(self, cls, id):
        key = (cls, id)
        return self._map.get(key)

8.5 PyTorch 中的弱引用

PyTorch 使用 WeakValueDictionary 管理张量缓存:

# PyTorch 内部(简化)
class TensorCache:
    _cache = weakref.WeakValueDictionary()

    @classmethod
    def get_or_create(cls, shape, dtype):
        key = (shape, dtype)
        tensor = cls._cache.get(key)
        if tensor is None:
            tensor = torch.empty(shape, dtype=dtype)
            cls._cache[key] = tensor
        return tensor

填空题知识点讲解

1. Python 中获取对象引用计数的函数是 ________。

sys.getrefcount


2. WeakValueDictionary 在 ________ 被回收时自动删除对应条目。

值对象


3. weakref.finalize 的回调函数中 ________(能/不能)访问被回收的对象。

不能(应使用静态方法,通过参数传递所需信息)


4. 使用 weakref.proxy 时,若目标对象已被回收,访问代理会抛出 ________ 异常。

ReferenceError


5. WeakSet 中的元素被回收时,会自动从集合中 ________。

移除

编程题知识点讲解

1. 实现对象池

实现一个对象池,使用弱引用跟踪借出的对象:

class ObjectPool:
    def __init__(self, factory, max_size=10):
        """factory: 创建对象的工厂函数"""
        # ...

    def acquire(self):
        """获取一个对象(从池中或新建)"""
        # ...

    def release(self, obj):
        """归还对象"""
        # ...
import weakref
from collections import deque
from typing import Callable, TypeVar

T = TypeVar("T")

class ObjectPool:
    """对象池,跟踪借出对象(弱引用)。"""

    def __init__(self, factory: Callable[[], T], max_size: int = 10):
        self._factory = factory
        self._max_size = max_size
        self._pool: deque[T] = deque()
        self._borrowed: weakref.WeakSet = weakref.WeakSet()

    def acquire(self) -> T:
        """获取一个对象。"""
        if self._pool:
            obj = self._pool.popleft()
        else:
            obj = self._factory()
        self._borrowed.add(obj)
        return obj

    def release(self, obj: T) -> None:
        """归还对象。"""
        if obj in self._borrowed:
            if len(self._pool) < self._max_size:
                self._pool.append(obj)
            # 否则丢弃(让 GC 回收)

    @property
    def borrowed_count(self) -> int:
        """当前借出数量。"""
        return len(self._borrowed)

# 测试
class Connection:
    _next_id = 0

    def __init__(self):
        Connection._next_id += 1
        self.id = Connection._next_id
        print(f"创建连接 {self.id}")

    def __repr__(self):
        return f"Connection({self.id})"

if __name__ == "__main__":
    pool = ObjectPool(factory=Connection, max_size=3)

    c1 = pool.acquire()
    c2 = pool.acquire()
    print(f"借出: {c1}, {c2}")
    print(f"当前借出数: {pool.borrowed_count}")

    pool.release(c1)
    print(f"归还后借出数: {pool.borrowed_count}")

    c3 = pool.acquire()  # 复用 c1
    print(f"再借出: {c3}")

2. 实现属性变更观察者

实现一个可观察的属性描述符,使用弱引用管理观察者:

class ObservableProperty:
    def __init__(self, default=None):
        # ...

    def __set_name__(self, owner, name):
        # ...

    def __get__(self, obj, objtype=None):
        # ...

    def __set__(self, obj, value):
        # ...

    def observe(self, obj, callback):
        """注册观察者(弱引用)"""
        # ...
import weakref
from typing import Callable, Any

class ObservableProperty:
    """可观察属性描述符。"""

    def __init__(self, default: Any = None) -> None:
        self._default = default
        self._storage_name: str = ""
        # 每个对象对应一个观察者列表(弱引用)
        self._observers: weakref.WeakKeyDictionary = weakref.WeakKeyDictionary()

    def __set_name__(self, owner: type, name: str) -> None:
        self._storage_name = f"_observable_{name}"

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self._storage_name, self._default)

    def __set__(self, obj, value):
        old = getattr(obj, self._storage_name, self._default)
        setattr(obj, self._storage_name, value)
        if old != value:
            self._notify(obj, old, value)

    def observe(self, obj, callback: Callable[[Any, Any], None]) -> None:
        """注册观察者。

        Args:
            obj: 被观察的对象实例。
            callback: 观察者函数(必须支持弱引用)。
        """
        if obj not in self._observers:
            self._observers[obj] = []
        self._observers[obj].append(callback)

    def _notify(self, obj, old, new):
        callbacks = self._observers.get(obj, [])
        for cb in callbacks:
            cb(old, new)

# 测试
class Config:
    debug = ObservableProperty(default=False)
    timeout = ObservableProperty(default=30)

if __name__ == "__main__":
    config = Config()

    def on_debug_change(old, new):
        print(f"debug: {old} -> {new}")

    def on_timeout_change(old, new):
        print(f"timeout: {old} -> {new}")

    Config.debug.observe(config, on_debug_change)
    Config.timeout.observe(config, on_timeout_change)

    config.debug = True   # 输出:debug: False -> True
    config.timeout = 60    # 输出:timeout: 30 -> 60

3. 实现资源管理器

实现一个资源管理器,使用 finalize 确保资源被清理:

class ResourceManager:
    def __init__(self):
        # ...

    def acquire(self, name: str) -> "Resource":
        """获取资源"""
        # ...

    def __del__(self):
        # ...
import weakref
from typing import Dict

class Resource:
    """受管资源。"""

    def __init__(self, name: str, manager: "ResourceManager") -> None:
        self.name = name
        self._manager = manager
        self._closed = False

    def use(self) -> str:
        """使用资源。"""
        if self._closed:
            raise RuntimeError("资源已关闭")
        return f"使用 {self.name}"

    def close(self) -> None:
        """手动关闭。"""
        if not self._closed:
            self._closed = True
            self._manager._release(self.name)
            print(f"关闭资源: {self.name}")

class ResourceManager:
    """资源管理器,使用 finalize 确保清理。"""

    def __init__(self) -> None:
        self._resources: Dict[str, Resource] = {}

    def acquire(self, name: str) -> Resource:
        """获取资源。"""
        if name in self._resources:
            return self._resources[name]

        resource = Resource(name, self)
        self._resources[name] = resource

        # 注册 finalizer,资源对象回收时自动释放
        weakref.finalize(resource, self._release, name)
        print(f"分配资源: {name}")
        return resource

    def _release(self, name: str) -> None:
        """释放资源。"""
        if name in self._resources:
            del self._resources[name]
            print(f"释放资源: {name}")

    @property
    def active_count(self) -> int:
        """活跃资源数。"""
        return len(self._resources)

# 测试
if __name__ == "__main__":
    import gc

    manager = ResourceManager()

    r1 = manager.acquire("db_connection")
    r2 = manager.acquire("file_handle")

    print(f"活跃数: {manager.active_count}")  # 2
    print(r1.use())

    r1.close()  # 手动关闭
    print(f"活跃数: {manager.active_count}")  # 1

    del r2
    gc.collect()  # 触发 finalize
    print(f"活跃数: {manager.active_count}")  # 0

10.1 标准与规范

10.2 学术论文

  • [5] McCarthy, J. (1960). Recursive functions of symbolic expressions and their computation by machine, Part I. Communications of the ACM, 3(4), 184-195. https://doi.org/10.1145/367177.367199
  • [6] Liskov, B., & Zilles, S. (1974). Programming with abstract data types. Proceedings of the ACM SIGPLAN Symposium on Very High Level Languages, 50-59. https://doi.org/10.1145/800233.807045
  • [7] Bacon, D. F., & Rajan, V. T. (2001). Concurrent cycle collection in reference counted systems. European Conference on Object-Oriented Programming, 207-235. https://doi.org/10.1007/3-540-45337-8_12

10.3 技术文档

10.4 书籍

  • [11] Ramalho, L. (2022). Fluent Python (2nd ed.). O’Reilly Media.
  • [12] Beazley, D., & Jones, B. K. (2013). Python Cookbook (3rd ed.). O’Reilly Media.
  • [13] Jones, M. T. (2023). Python Object-Oriented Programming (4th ed.). Packt Publishing.

11. 进一步阅读

11.1 进阶主题

  1. CPython weakref 源码分析:阅读 Modules/_weakref.c 与 Objects/weakrefobject.c,理解弱引用的 C 层实现;
  2. free-threaded 模式下的弱引用:PEP 703 引入的无 GIL 构建对弱引用语义的影响;
  3. 第三方库:wrapt(弱引用代理)、weakreflist(弱引用列表);
  4. 分布式对象缓存:Redis + 弱引用的混合方案;
  5. 跨语言对比:Java ReferenceQueue、C++ weak_ptr::lock()、Rust Weak<T>::upgrade()。

11.2 相关论文

  • “Reference Counting” - George Collins (1960)
  • “On the Cost of Concurrent Garbage Collection” - Kafura et al. (1992)
  • “A Unified Theory of Garbage Collection” - Bacon et al. (2004)

11.3 实战项目

  1. 实现 LRU 缓存:结合 WeakValueDictionary 与 OrderedDict;
  2. 实现事件总线:基于 WeakSet 的发布订阅系统;
  3. 实现对象关系映射:参考 SQLAlchemy 的 IdentityMap;
  4. 资源池管理:数据库连接池、线程池;
  5. 内存分析工具:统计弱引用使用情况,识别内存泄漏。

11.5 视频课程

  • Ned Batchelder - Python Memory Management(PyCon 2019)
  • Larry Hastings - Memory in Python(PyCon 2016)
  • Anthony Shaw - CPython Internals(PyCon 2022)

附录 A:weakref API 速查表

A.1 核心函数

函数说明
weakref.ref(obj, callback=None)创建弱引用
weakref.proxy(obj, callback=None)创建弱引用代理
weakref.getweakrefcount(obj)获取对象的弱引用数
weakref.getweakrefs(obj)获取对象的所有弱引用列表
weakref.finalize(obj, func, *args, **kwargs)创建 finalizer

A.2 容器

容器说明
WeakKeyDictionary()弱键字典
WeakValueDictionary()弱值字典
WeakSet()弱集合
WeakMethod(method)弱方法引用
WeakFinalizer()finalizer 对象

A.3 类型对象

类型说明
ReferenceTypeweakref.ref 的类型
ProxyTypeweakref.proxy 的类型
CallableProxyType可调用代理类型
ProxyTypes所有代理类型的元组

附录 B:类型支持矩阵

类型支持弱引用备注
用户自定义类√默认支持
函数 def√
方法(绑定)√通过 WeakMethod
类型对象 type√
bytearray√
memoryview√
array.array√
socket√
int×子类化可支持
float×子类化可支持
complex×
str×子类化可支持
bytes×
tuple×
frozenset×
list×子类化可支持
dict×子类化可支持
set×子类化可支持
bool×
NoneType×

附录 C:性能基准

操作耗时
创建 weakref.ref~100ns
调用 ref()~50ns
弱引用回调~1μs
WeakKeyDictionary 查找~150ns
WeakValueDictionary 查找~150ns
WeakSet 添加~200ns
finalize 注册~500ns
finalize 调用~1μs

附录 D:CPython 内部实现

D.1 PyWeakReference 结构(简化)

typedef struct _PyWeakReference {
    PyObject_HEAD
    PyObject *wr_object;     // 目标对象
    PyObject *wr_callback;   // 回调函数
    PyWeakReference *wr_prev; // 弱引用链表(前驱)
    PyWeakReference *wr_next; // 弱引用链表(后继)
} PyWeakReference;

D.2 对象的弱引用链表

每个支持弱引用的对象都有一个 __weakref__ 槽位,指向其所有弱引用的链表头:

typedef struct _object {
    Py_ssize_t ob_refcnt;      // 引用计数
    PyTypeObject *ob_type;
    // ...
    PyWeakReference *ob_weakref;  // 弱引用链表头(仅当支持时)
} PyObject;

D.3 回收回调流程

  1. GC 决定回收对象 oo;
  2. 遍历 oo 的弱引用链表;
  3. 对每个弱引用 rr:
    • 设置 r.wr_object=NULLr.\text{wr\_object} = \text{NULL};
    • 若 rr 有回调,将 rr 加入回调队列;
  4. 调用所有回调(按注册顺序);
  5. 释放对象内存。

附录 E:相关标准库模块

  • gc:垃圾回收器接口;
  • sys:getrefcount、getsizeof;
  • ctypes:C 级别弱引用操作;
  • multiprocessing.shared_memory:跨进程共享内存;
  • tracemalloc:内存分配追踪。

弱引用基础

基本写法:创建弱引用 weakref.ref(<对象>)

# 创建弱引用
import weakref

class Obj:
    pass

obj = Obj()
r = weakref.ref(obj)
print(r())        # 引用对象
print(r() is obj) # True

基本写法:访问引用对象 r()

# 调用弱引用获取对象
obj_ref = r()
if obj_ref is not None:
    print("对象存在")
else:
    print("对象已回收")

基本写法:对象回收后 del <对象>

# 删除强引用后弱引用返回 None
del obj
print(r())  # None

WeakValueDictionary

基本写法:值弱引用字典 weakref.WeakValueDictionary()

# 字典值为弱引用,对象可被回收
d = weakref.WeakValueDictionary()
o = Obj()
d["key"] = o
print(d["key"] is o)  # True
del o
print("key" in d)     # False,对象回收后自动移除

WeakKeyDictionary

基本写法:键弱引用字典 weakref.WeakKeyDictionary()

# 字典键为弱引用
d = weakref.WeakKeyDictionary()
o = Obj()
d[o] = "value"
print(d.get(o))  # value
del o
print(len(d))    # 0

WeakSet

基本写法:弱引用集合 weakref.WeakSet()

# 集合中元素为弱引用
s = weakref.WeakSet()
o = Obj()
s.add(o)
print(o in s)  # True
del o
print(len(s))  # 0

finalize 终结回调

基本写法:注册终结回调 weakref.finalize(<对象>, <函数>, *<参数>)

# 对象回收时调用回调
def cleanup(name):
    print(f"{name} 被回收")

o = Obj()
f = weakref.finalize(o, cleanup, "myobj")
del o  # 打印 "myobj 被回收"

基本写法:取消终结 f.detach()

# 取消终结器
f = weakref.finalize(o, cleanup, "myobj")
f.detach()  # 取消回调

基本写法:检查是否存活 f.alive

# 检查终结器是否仍存活
print(f.alive)

WeakMethod 方法弱引用

基本写法:方法弱引用 weakref.WeakMethod(<绑定方法>)

# 对绑定方法创建弱引用
class Service:
    def run(self):
        pass

s = Service()
m = weakref.WeakMethod(s.run)
print(m() is s.run)

proxy 代理

基本写法:创建代理 weakref.proxy(<对象>)

# 代理对象自动解引用
obj = Obj()
p = weakref.proxy(obj)
print(p is obj)  # False,但行为像 obj
del obj
# 访问 p 现在会抛出 ReferenceError

基本写法:代理回调 weakref.proxy(<对象>, <回调>)

# 代理对象回收时回调
def on_unref(ref):
    print("代理对象被回收")

p = weakref.proxy(obj, on_unref)

支持弱引用的对象

基本写法:检查是否支持弱引用 weakref.ref(<对象>)

# 内置类型如 list/dict 不支持弱引用
try:
    weakref.ref([1, 2, 3])
except TypeError as e:
    print(e)

基本写法:子类化获得支持 class <类>(dict): __slots__ = ("__weakref__",)

# 通过 __slots__ 让对象支持弱引用
class MyDict(dict):
    __slots__ = ("__weakref__",)

应用场景

基本写法:缓存弱引用 WeakValueDictionary

# 缓存大对象,不阻止回收
class Cache:
    def __init__(self):
        self._cache = weakref.WeakValueDictionary()
    def get(self, key, factory):
        obj = self._cache.get(key)
        if obj is None:
            obj = factory()
            self._cache[key] = obj
        return obj

基本写法:观察者模式弱引用 WeakSet

# 观察者列表使用弱引用,避免内存泄漏
class Subject:
    def __init__(self):
        self._observers = weakref.WeakSet()
    def subscribe(self, obs):
        self._observers.add(obs)
    def notify(self, msg):
        for obs in self._observers:
            obs.update(msg)

getweakrefcount

基本写法:统计弱引用数 weakref.getweakrefcount(<对象>)

# 返回指向对象的弱引用数
print(weakref.getweakrefcount(obj))

基本写法:获取所有弱引用 weakref.getweakrefs(<对象>)

# 返回所有弱引用列表
print(weakref.getweakrefs(obj))