weakref 模块

Python weakref 模块详解——弱引用、代理、finalize 与基于弱引用的缓存(WeakValueDictionary/WeakKeyDictionary/WeakSet)

分类: stdlib 难度: 中级 更新: 2025-08-14
弱引用 缓存 垃圾回收 对象生命周期 代理 字典

weakref 模块

📝 概述

weakref 模块提供“弱引用”(weak reference)支持。弱引用不会增加对象的引用计数,因此当对象只被弱引用持有时,仍可被垃圾回收器回收。它常用于构建缓存、避免循环引用导致的内存泄露、以及对对象生命周期进行观察与回调(finalize)。weakref 还提供基于弱引用的容器类型,如 WeakValueDictionary、WeakKeyDictionary 和 WeakSet。

与强引用不同,弱引用不保证对象存活;当目标对象被回收后,弱引用会变为“死亡引用”,返回 None 或在代理访问时报错。

🎯 学习目标

  • 理解强引用与弱引用的区别,以及弱引用的使用动机
  • 掌握 weakref 的核心 API:ref、proxy、finalize 与弱引用容器
  • 能够使用 WeakValueDictionary/WeakKeyDictionary 实现自动失效的缓存
  • 了解 finalize 的用法与 atexit 行为,正确处理资源回收回调
  • 掌握常见陷阱:循环引用与 del、代理 ReferenceError、可弱引用对象的限制

📋 前置知识

  • Python 对象模型与引用计数的基础概念
  • 垃圾回收(gc)与循环引用的基本原理
  • 类与属性、描述符与 property 的基本用法

🔍 详细内容

基本概念

  • 强引用:普通变量绑定的引用,会增加对象引用计数,保证对象不被回收。
  • 弱引用:不增加引用计数的引用句柄,不能单独阻止对象被回收。
  • 循环引用问题:两个对象相互引用可能导致引用计数无法归零;若还实现了 del,gc 可能无法安全回收并将对象放入 gc.garbage。
  • 解决思路:将其中一条边替换为弱引用,打破强引用环,或使用 weakref 的弱引用容器与回调机制管理生命周期。

核心 API 总览

  • weakref.ref(obj, callback=None):创建指向 obj 的弱引用,调用 r() 返回目标或 None。目标回收时执行回调,回调参数是弱引用对象本身。
  • weakref.proxy(obj, callback=None):创建 obj 的代理对象,像原对象一样使用;对象回收后访问代理会抛出 ReferenceError。
  • weakref.finalize(obj, func, *args, **kwargs):当 obj 被回收时调用 func。返回的 finalize 对象有属性 atexit 控制进程退出阶段是否兜底调用(默认 True)。
  • 容器类型:
    • WeakValueDictionary:对“值”持弱引用,值不再被其他强引用持有时会自动从字典中移除。
    • WeakKeyDictionary:对“键”持弱引用,键不再被其他强引用持有时条目会自动移除。
    • WeakSet:对元素持弱引用,元素无强引用后自动从集合中消失。

语法与示例

1. 通过弱引用打破循环引用

下面的示例展示父子节点的双向引用关系引发的循环引用问题,以及如何用 weakref.ref 打破强引用环:

# -*- coding: utf-8 -*-
import weakref

class Node(object):
    def __init__(self, data):
        self.data = data
        self._parent = None
        self.children = []

    @property
    def parent(self):
        # _parent 保存的是 weakref.ref 对象,需要调用()解引用
        return None if self._parent is None else self._parent()

    @parent.setter
    def parent(self, node):
        # 目标对象被回收时触发回调
        self._parent = weakref.ref(node, lambda r: print('__del__', r))

    def add_child(self, child):
        self.children.append(child)
        child.parent = self

n1 = Node(0)
n2 = Node(2)
n1.add_child(n2)
# 删除 n1 后,如果 n2.parent 只是弱引用,n1 仍可被回收
# 输出示例: __del__ <weakref at 0x...; dead>

2. 代理对象 proxy:像原对象一样使用

import weakref

class Node:
    def __init__(self, data):
        self.data = data

n = Node(10)
p = weakref.proxy(n)  # 创建代理
print(p.data)          # 10,与 n.data 等价

# 当 n 被删除后再访问 p,会抛出 ReferenceError
# del n
# print(p.data)  # ReferenceError: weakly-referenced object no longer exists

3. finalize:对象回收时执行回调

import weakref

class ExpensiveObject:
    def __del__(self):
        print('(Deleting {})'.format(self))

def on_finalize(*args):
    print('on_finalize({!r})'.format(args))

obj = ExpensiveObject()
f = weakref.finalize(obj, on_finalize, 'extra argument')
# 可选:控制在进程退出阶段是否兜底调用
f.atexit = True

# 删除强引用后,obj 被回收时将调用 on_finalize
# del obj

注意:如果将被跟踪对象(obj)自身作为回调参数或保存在闭包中,可能无意中保留了强引用,导致对象无法回收;谨慎设计回调签名。

4. 基于弱引用的缓存:WeakValueDictionary

# 使用 WeakValueDictionary 让未被他处使用的缓存项自动过期
import weakref

class CachedSpamManager:
    def __init__(self):
        self._cache = weakref.WeakValueDictionary()

    def get_spam(self, name):
        if name not in self._cache:
            s = Spam(name)
            self._cache[name] = s
        return self._cache[name]

class Spam:
    def __init__(self, name):
        self.name = name

m = CachedSpamManager()
a = m.get_spam('foo')
b = m.get_spam('bar')
# 如果外部没有保存对某个 Spam 的强引用,对应缓存会在回收后自动移除

5. WeakKeyDictionary 与 WeakSet 简述

  • WeakKeyDictionary:常用于以对象作为键的映射(如实例元数据表),当键对象不再使用时,条目自动清理。
  • WeakSet:集合元素以弱引用持有,元素对象生命周期结束即从集合淡出,无需手动删除。

参数说明(核心函数)

函数 关键参数 说明
weakref.ref(obj, callback=None) obj, callback 创建指向 obj 的弱引用;obj 回收时调用 callback(reference)
weakref.proxy(obj, callback=None) obj, callback 创建代理;目标回收后访问属性/方法会抛 ReferenceError
weakref.finalize(obj, func, *args, **kwargs) obj, func 关联回调;通过 f.atexit 控制进程退出时的兜底执行
weakref.WeakValueDictionary() 值以弱引用保存,值无强引用后自动剔除
weakref.WeakKeyDictionary() 键以弱引用保存,键无强引用后自动剔除
weakref.WeakSet() 元素以弱引用保存,元素无强引用后自动剔除

返回值

  • ref 返回的弱引用对象可调用:r() → 目标或 None。
  • proxy 返回的代理与目标接口一致,但在目标死亡后访问会抛 ReferenceError。
  • finalize 返回的对象可控制 atexit 属性与是否已调用等状态。
  • 弱引用容器的行为与对应内置容器类似,但元素/键/值的存在不阻止回收。

💡 实际应用

缓存与对象池

  • 使用 WeakValueDictionary 维护对象缓存,避免长期占用内存;当外部不再使用对象时,缓存项自动清理。

观察对象生命周期

  • 使用 weakref.ref 配合回调,或使用 finalize,在对象回收时执行清理逻辑(如关闭句柄、释放外部资源、更新监控指标)。

打破循环引用

  • 在双向关系建模(如父子、图结构)中,将至少一侧改为弱引用,避免循环引用阻止回收。

⚠️ 注意事项

  • 并非所有对象都支持弱引用:某些内建类型不支持;自定义类若使用 slots,需包含 “weakref” 槽位才可被弱引用。
  • 使用 proxy 访问已被回收的对象会抛 ReferenceError,访问前需确保对象存活或捕获异常。
  • 当类实现 del 时,参与循环引用的对象可能无法被自动回收(进入 gc.garbage);可通过弱引用设计或显式打破环。
  • finalize 的回调若意外持有被跟踪对象的强引用(作为参数或闭包捕获),会阻止对象回收,谨慎设计回调签名与参数。

🔗 相关内容

📚 扩展阅读

  • Python 官方文档 - weakref 模块: https://docs.python.org/3/library/weakref.html
  • Python 官方文档 - 数据模型与垃圾回收: https://docs.python.org/3/reference/datamodel.html

🏷️ 标签

弱引用 缓存 垃圾回收 代理 finalize WeakValueDictionary WeakKeyDictionary WeakSet


原始文档内容参考

以下为迁移前的原始文档,完整保留了文本、代码与图片,便于对照与回溯。

一、例子

import weakref

class CachedSpamManager:
    def __init__(self):
        self._cache = weakref.WeakValueDictionary()
    def get_spam(self, name):
        if name not in self._cache:
            s = Spam(name)
            self._cache[name] = s
        else:
            s = self._cache[name]
        return s
      
class Spam:
    def __init__(self, name):
        self.name = name

Spam.manager = CachedSpamManager()

def get_spam(name):
    return Spam.manager.get_spam(name)

if __name__ == '__main__':
    a = get_spam('foo')
    b = get_spam('bar')
    print('a is b:', a is b)
    c = get_spam('foo')
    print('a is c:', a is c)

二、模块

—–weakref—–

class Node(object):

    def __init__(self, data):
        self.data = data
        self.parent = None
        self.children = []

    def add_child(self, child):
        self.children.append(child)
        child.parent = self

    def __del__(self):
        print '__del__'

n = Node(0)
del n
# __del__
n1 = Node(1)
n2 = Node(2)
n1.add_child(n2)
del n1 # no output
n2.parent
# <__main__.Node at 0x7fd87ad5c250>


双亲节点的指针指向孩子节点,孩子节点又指向双亲节点。这构成了循环引用,所以每个对象的引用计数都不可能变成 0

我们可以手动使用 gc 模块来进行垃圾回收

import gc

gc.collect()


糟糕的是,我们这里循环引用的对象都实现了 del 方法,gc 模块不会销毁这些对象,因为 gc 模块不知道应该先调用哪个对象的 del 方法。gc模块会把这样的对象放到 gc.garbage 中,并不会销毁对象。

n1 = Node(1)
n2 = Node(2)
print n1, n2
# <__main__.Node object at 0x7f94109906d0> <__main__.Node object at 0x7f9410990610>
n1.add_child(n2)
del n1
del n2
gc.collect()
# 64
gc.garbage
# [<__main__.Node object at 0x7f94109906d0> <__main__.Node object at 0x7f9410990610>]


我们可以通过 weakref 来解决,如果一个对象只剩下一个弱引用,那么它是可以被垃圾回收的

import weakref

class Node(object):

    def __init__(self, data):
        self.data = data
        self._parent = None
        self.children = []

    @property
    def parent(self):
        return None if self._parent is None else self._parent()

    @parent.setter
    def parent(self, node):
        self._parent = weakref.ref(node, callback)

    def add_child(self, child):
        self.children.append(child)
        child.parent = self

def callback(ref):
    print '__del__', ref

n1 = Node(0)
n2 = Node(2)
print n1,n2
# <__main__.Node object at 0x7fb0c2750c10> <__main__.Node object at 0x7fb0c2750d10>
n1.add_child(n2)
del n1
# __del__ <weakref at 0x7fb0c26e75d0; dead>


不过,如果我们使用 weakref.ref() 创建弱引用,每次使用时都需要形如这样 xx() 来获取,有一点别扭。 可以通过 weakref.proxy() 这种来避免 () 操作。

n = Node(10)
p = weakref.proxy(n)
p.data
# 10

1. weakref对象的非永久引用

weakref模块支持对象的弱引用。正常的引用会增加对象的引用数,并避免它被垃圾回收。但结果并不总是如期望中的那样,比如有时可能会出现一个循环引用,或者有时需要内存时可能要删除对象的缓存。弱引用(weak reference)是一个不能避免对象被自动清理的对象句柄

1.1 引用

对象的弱引用要通过ref类来管理。要获取原对象,可以调用引用对象。

import weakref
class ExpensiveObject:
    def __del__(self):
        print('(Deleting {})'.format(self))
obj = ExpensiveObject()
r = weakref.ref(obj)
print('obj:', obj)
print('ref:', r)
print('r():', r())
print('deleting obj')
del obj
print('r():', r())

在这里,由于obj在第二次调用引用之前已经被删除,所以ref返回None。

1.2 引用回调 

ref构造函数接受一个可选的回调函数,删除所引用的对象时会调用这个函数。

import weakref
class ExpensiveObject:
    def __del__(self):
        print('(Deleting {})'.format(self))
def callback(reference):
    """Invoked when referenced object is deleted"""
    print('callback({!r})'.format(reference))
obj = ExpensiveObject()
r = weakref.ref(obj, callback)
print('obj:', obj)
print('ref:', r)
print('r():', r())
print('deleting obj')
del obj
print('r():', r())

当引用已经“死亡”而且不再引用原对象时,这个回调会接受这个引用对象作为参数。这个特性的一种用法就是从缓存中删除弱引用对象。

1.3 最终化对象

清理弱引用时要对资源完成更健壮的管理,可以使用finalize将回调与对象关联。finalize实例会一直保留(直到所关联的对象被删除) ,即使应用并没有保留最终化对象的引用。

import weakref
class ExpensiveObject:
    def __del__(self):
        print('(Deleting {})'.format(self))
def on_finalize(*args):
    print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
weakref.finalize(obj, on_finalize, 'extra argument')
del obj

finalize的参数包括要跟踪的对象,对象被垃圾回收时要调用的callable,以及传入这个callable的所有位置或命名参数。

这个finalize实例有一个可写属性atexit,用来控制程序退出时是否调用这个回调(如果还未调用)。 

import sys
import weakref
class ExpensiveObject:
    def __del__(self):
        print('(Deleting {})'.format(self))
def on_finalize(*args):
    print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
f = weakref.finalize(obj, on_finalize, 'extra argument')
f.atexit = bool(int(sys.argv[1]))

默认设置是调用这个回调。将atexit设置为false会禁用这种行为。

如果向finalize实例提供所跟踪对象的一个引用,这便会导致一个引用被保留,所以这个对象永远不会被垃圾回收。 

import gc
import weakref
class ExpensiveObject:
    def __del__(self):
        print('(Deleting {})'.format(self))
def on_finalize(*args):
    print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
obj_id = id(obj)
f = weakref.finalize(obj, on_finalize, obj)
f.atexit = False
del obj
for o in gc.get_objects():
    if id(o) == obj_id:
        print('found uncollected object in gc')

如上所示,尽管obj的显式引用已经删除,但是这个对象仍保留,通过f对垃圾回收器可见。

使用所跟踪对象的一个绑定方法作为callable也可以适当地避免对象最终化。

import gc
import weakref
class ExpensiveObject:
    def __del__(self):
        print('(Deleting {})'.format(self))
    def do_finalize(self):
        print('do_finalize')
obj = ExpensiveObject()
obj_id = id(obj)
f = weakref.finalize(obj, obj.do_finalize)
f.atexit = False
del obj
for o in gc.get_objects():
    if id(o) == obj_id:
        print('found uncollected object in gc')

由于为finalize提供的callable是实例obj的一个绑定方法,所以最终化方法保留了obj的一个引用,它不能被删除和被垃圾回收。

1.4 代理

有时使用代理比较弱引用更方便。使用代理可以像使用原对象一样,而且不要求在访问对象之前先调用代理。这说明,可以将代理传递到一个库,而这个库并不知道它接收的是一个引用而不是真正的对象。

import weakref
class ExpensiveObject:
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print('(Deleting {})'.format(self))
obj = ExpensiveObject('My Object')
r = weakref.ref(obj)
p = weakref.proxy(obj)
print('via obj:', obj.name)
print('via ref:', r().name)
print('via proxy:', p.name)
del obj
print('via proxy:', p.name)

如果引用对象被删除后再访问代理,会产生一个ReferenceError异常。

1.5 缓存对象

ref和proxy类被认为是“底层”的。尽管它们对于维护单个对象的弱引用很有用,并且还支持对循环引用的垃圾回收,但WeakKeyDictionary和WeakValueDictionary类为创建多个对象的缓存提供了一个更适合的API。

WeakValueDictionary类使用它包含的值的弱引用,当其他代码不再真正使用这些值时,则允许垃圾回收。利用垃圾回收器的显式调用,下面展示了使用常规字典和WeakValueDictionary完成内存处理的区别。 

import gc
from pprint import pprint
import weakref
gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
class ExpensiveObject:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return 'ExpensiveObject({})'.format(self.name)
    def __del__(self):
        print('    (Deleting {})'.format(self))

def demo(cache_factory):
    # hold objects so any weak references
    # are not removed immediately
    all_refs = {}
    # create the cache using the factory
    print('CACHE TYPE:', cache_factory)
    cache = cache_factory()
    for name in ['one', 'two', 'three']:
        o = ExpensiveObject(name)
        cache[name] = o
        all_refs[name] = o
        del o  # decref
 
    print('  all_refs =', end=' ')
    pprint(all_refs)
    print('\n  Before, cache contains:', list(cache.keys()))
    for name, value in cache.items():
        print('    {} = {}'.format(name, value))
        del value  # decref
 
    # remove all references to the objects except the cache
    print('\n  Cleanup:')
    del all_refs
    gc.collect()
    print('\n  After, cache contains:', list(cache.keys()))
    for name, value in cache.items():
        print('    {} = {}'.format(name, value))
    print('  demo returning')
    return

demo(dict)
print()
demo(weakref.WeakValueDictionary)

如果循环变量指示所缓存的值,那么这些循环变量必须被显式清除,以使对象的引用数减少。否则,垃圾回收器不会删除这些对象,它们仍然会保留在缓存中。类似地,all_refs变量用来保存引用,以防止它们被过早地垃圾回收。

WeakKeyDictionary的工作与之类似,不过使用了字典中键的弱引用而不是值的弱引用

作者: Python教程

版本: 1.0

讨论与反馈

欢迎在下方留言讨论,分享你的学习心得或提出问题。评论基于GitHub Issues,需要GitHub账号。