弱引用
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 演进时间线
| 时间 | 版本 | 重要变化 |
|---|---|---|
| 1994 | Python 1.0 | 仅有 sys.getrefcount,无弱引用支持 |
| 2001 | Python 2.1 | PEP 205 正式引入 weakref 模块 |
| 2003 | Python 2.3 | 新式类默认支持弱引用;WeakKeyDictionary、WeakValueDictionary 稳定 |
| 2008 | Python 2.6 | weakref.proxy 增加 callback 参数 |
| 2010 | Python 2.7 | WeakSet 加入标准库 |
| 2014 | Python 3.4 | PEP 442 安全对象 finalizer,弱引用回调时机更可预测 |
| 2018 | Python 3.7 | weakref.finalize 加入 atexit=True 选项 |
| 2021 | Python 3.10 | weakref.ref 支持 __class_getitem__,类型注解友好 |
| 2023 | Python 3.12 | WeakSet 支持 typing.MutableSet 协议;性能优化 |
| 2025 | Python 3.13+ | 弱引用在 free-threaded 构建(PEP 703)中的语义调整 |
1.3 PEP 205 的设计目标
PEP 205(Weak References in Python,由 Fred L. Drake, Jr. 与 Neil Schemenauer 于 2001 年提出)明确了弱引用的设计目标:
- 不增加引用计数:弱引用对对象”零”持有,不阻止 GC;
- 类型约束最小化:除少数内置不可变类型(
int、str、tuple、list、dict)外,自定义类默认支持; - 可扩展容器:提供
WeakKeyDictionary、WeakValueDictionary、WeakSet三类标准容器; - 回调机制:对象被回收时可触发回调,便于资源清理;
- 代理对象:
proxy提供透明访问,简化调用语法。
1.4 与其他语言的对比
| 语言 | 弱引用类型 | 主要用途 |
|---|---|---|
| Java | WeakReference、SoftReference、PhantomReference | 缓存、Finalization |
| C++ | std::weak_ptr | 配合 shared_ptr 解决循环引用 |
| C# | WeakReference<T>、WeakReference | 缓存 |
| Go | runtime.KeepAlive + finalizer | 资源清理 |
| Rust | Weak<T> | 配合 Rc/Arc 解决循环引用 |
| JavaScript | WeakMap、WeakSet | 元数据附加、对象追踪 |
| Swift | weak、unowned | 闭包与代理模式 |
| Python | weakref.ref、Weak* | 缓存、观察者、单例 |
2. 形式化定义
2.1 对象图的代数定义
设 为对象集合, 为引用关系。对象 的引用集合定义为:
强引用计数:
可达性:从根集 (栈变量、全局变量)出发,强可达对象集合定义为:
其中 为最小不动点算子。
垃圾对象: 且 (仅考虑强引用)。
2.2 弱引用的形式化
弱引用是一类特殊引用,不参与可达性分析:
形式化地,弱引用 满足:
当对象 的所有强引用消失()时, 被 GC 回收,所有弱引用 自动变为”失效”(dereferenced), 返回 None。
2.3 弱引用的有效性条件
Python 对象支持弱引用的充要条件:
其中:
- 包括:用户自定义类、函数、方法(bound/unbound)、类型对象、
abstractmethod、bytearray、memoryview、array.array、socket等; - 非 包括:
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 弱引用回调的时序
当对象 被回收时,弱引用回调按以下顺序执行:
形式化地:
关键性质:回调执行时对象 已部分析构,不可在回调中重新访问 (会得到 None 或已失效的对象)。
2.5 Weak* 容器的语义
| 容器 | 键 | 值 | 回收触发条件 |
|---|---|---|---|
WeakKeyDictionary | 弱引用 | 强引用 | 键对象被回收 |
WeakValueDictionary | 强引用 | 弱引用 | 值对象被回收 |
WeakSet | 元素(弱引用) | - | 元素被回收 |
形式化地,WeakKeyDictionary 满足:
当 ,GC 回收 ,触发弱键回调,从字典中删除 条目。
3. 理论推导与原理解析
3.1 引用计数模型
CPython 默认使用引用计数(reference counting)作为主要 GC 策略。每个对象 维护一个引用计数 :
引用计数变更时机:
| 事件 | 变化 |
|---|---|
赋值 a = obj | |
del a 或 a 离开作用域 | |
加入容器 lst.append(obj) | |
从容器移除 lst.remove(obj) | |
函数调用 f(obj) | |
| 函数返回 |
当 时,对象立即被回收。
3.2 循环引用问题
引用计数无法处理循环引用:
但 实际上已不可达。Python 通过 分代标记-清除 GC 处理:
- 周期性扫描容器对象,构建对象图;
- 从根集出发标记可达对象;
- 清除不可达的容器对象。
弱引用的作用:将循环引用中的一边改为弱引用,破坏循环:
class Parent:
def __init__(self):
self.children = [] # 强引用子对象
class Child:
def __init__(self, parent):
self._parent_ref = weakref.ref(parent) # 弱引用父对象
parent.children.append(self)
此时 不含 child 的引用,parent 可被正常回收。
3.3 三代分代 GC 的数学模型
CPython 分代 GC 将对象分为三代:
阈值(threshold):
当某代分配数 - 释放数 时,触发该代 GC。
弱引用与 GC 的协作:
- GC 在回收对象前,先清除所有指向该对象的弱引用;
- 调用弱引用的回调函数(如果设置了);
- 实际释放对象内存。
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
| 维度 | WeakValueDictionary | WeakKeyDictionary |
|---|---|---|
| 键 | 强引用(普通对象) | 弱引用(必须支持弱引用) |
| 值 | 弱引用(必须支持弱引用) | 强引用(普通对象) |
| 回收触发 | 值对象被回收 | 键对象被回收 |
| 典型用途 | 缓存(按 key 存储对象) | 元数据(按对象附加信息) |
| 内部实现 | 键 + 弱值引用 + 回调 | 弱键引用 + 值 |
| 性能 | 略慢(需管理弱值) | 略快 |
5.2 weakref.ref vs weakref.proxy
| 维度 | ref | proxy |
|---|---|---|
| 访问方式 | 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 weakref | Java WeakReference |
|---|---|---|
| 引用层级 | 单层(弱) | 三层(Weak、Soft、Phantom) |
| Soft 语义 | 无(需自行实现) | 内置(内存不足时回收) |
| Phantom 语义 | 无(finalize 替代) | 内置(finalization 后回收) |
| ReferenceQueue | 无 | 有 |
| 性能 | 较高 | 中等(需 GC 协调) |
5.5 weakref vs JavaScript WeakMap
| 维度 | Python weakref | JavaScript 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 最佳实践总结
- 优先使用 finalize 而非 del:更安全,可测试;
- 避免在回调中访问对象:仅使用闭包外部捕获的信息;
- 显式声明 weakref 槽位:使用
__slots__时; - 跨线程加锁:访问弱引用容器时;
- 不依赖回调时序:弱引用回调可能延迟执行;
- 使用弱引用打破循环:在父子、观察者等结构中;
- 测试时强制 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 标准与规范
- [1] Python Software Foundation. Python Language Reference - Data Model. Python 3.12. https://docs.python.org/3.12/reference/datamodel.html
- [2] PEP 205 - Weak References. 2001. https://peps.python.org/pep-0205/
- [3] PEP 442 - Safe object finalization. 2013. https://peps.python.org/pep-0442/
- [4] PEP 703 - Making the Global Interpreter Lock Optional. 2023. https://peps.python.org/pep-0703/
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 技术文档
- [8] weakref — Weak references. Python 3.12 Documentation. https://docs.python.org/3.12/library/weakref.html
- [9] gc — Garbage Collector interface. Python 3.12 Documentation. https://docs.python.org/3.12/library/gc.html
- [10] CPython Source Code: Modules/_weakref.c. https://github.com/python/cpython/blob/main/Modules/_weakref.c
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 进阶主题
- CPython
weakref源码分析:阅读Modules/_weakref.c与Objects/weakrefobject.c,理解弱引用的 C 层实现; - free-threaded 模式下的弱引用:PEP 703 引入的无 GIL 构建对弱引用语义的影响;
- 第三方库:
wrapt(弱引用代理)、weakreflist(弱引用列表); - 分布式对象缓存:Redis + 弱引用的混合方案;
- 跨语言对比:Java
ReferenceQueue、C++weak_ptr::lock()、RustWeak<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 实战项目
- 实现 LRU 缓存:结合
WeakValueDictionary与OrderedDict; - 实现事件总线:基于
WeakSet的发布订阅系统; - 实现对象关系映射:参考 SQLAlchemy 的
IdentityMap; - 资源池管理:数据库连接池、线程池;
- 内存分析工具:统计弱引用使用情况,识别内存泄漏。
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 类型对象
| 类型 | 说明 |
|---|---|
ReferenceType | weakref.ref 的类型 |
ProxyType | weakref.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 回收回调流程
- GC 决定回收对象 ;
- 遍历 的弱引用链表;
- 对每个弱引用 :
- 设置 ;
- 若 有回调,将 加入回调队列;
- 调用所有回调(按注册顺序);
- 释放对象内存。
附录 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))