正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。如果我们使用一个全局变量引用了一个对象,那直到程序退出前,对象的内存都不会被释放掉。此时,我们可以使用弱引用。什么是弱引用?弱引用不会增加对象的引用数量,引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。
import weakref
_ = {0, 1}
wref = weakref.ref(_)
print(wref()) # {0, 1}
_ = {2,3,4}
print(wref()) # None
我们可以看到,原来 wref 引用的是 {0, 1}。但是, 当 _ 指向 {2, 3, 4} 之后,{0, 1} 不再有对象引用它了,所以它被垃圾回收了,导致之后输出 wref() 时为 None。
弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。
WeakValueDictionary类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从WeakValueDictionary中删除。因此,WeakValueDictionary经常用于缓存。
>>> import weakref
>>> class Student:
... def __init__(self, name):
... self.name = name
... def __repr__(self):
... return f'{Student}self.name'
...
>>> stock = weakref.WeakValueDictionary()
>>> s_list = [Student("Ming"), Student("Hong"), Student("Gang")]
>>> for student in s_list: # 临时变量student引用了对象,这可能会导致该变量的存在时间比预期长。
... stock[s.name] = student
...
>>> sorted(stock.keys())
['Gang', 'Hong', 'Ming']
>>> del s_list # 删除引用了三个 Student 的list
>>> sorted(stock.keys()) # student最后引用的是"Gang"。
['Gang']
>>> del student # for循环中的变量student是全局变量,除非显式删除,否则不会消失。
>>> sorted(stock.keys())
[]
weakref模块还提供了WeakSet类,按照文档的说明,这个类的作用很简单:“保存元素弱引用的集合类。元素没有强引用时,集合会把它删除。”如果一个类需要知道所有实例,一种好的方案是创建一个WeakSet类型的类属性,保存实例的引用。如果使用常规的set,实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与Python进程一样长,除非显式删除类。
我们来看看实例:
"""
如果一个类需要知道所有实例,一种好的方案是创建一个WeakSet类型的类属性,保存实例的引用。如果使用常规的set,实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与Python进程一样长,除非显式删除类。
"""
import weakref
class Student:
stock_set = weakref.WeakSet()
def __new__(cls, *args, **kwargs):
new_one = super().__new__(cls) # 调用父类object初始化
cls.stock_set.add(new_one) # 存储实例对象的弱引用
return new_one
def __init__(self, name):
self.name = name
def func(self):
print(self.name)
def test():
s1, s2 = Student("XiaoMing"), Student("XiaoWang")
print(Student.stock_set.data)
# {<weakref at 0x0000020667B6E1D8; to 'Student' at 0x0000020676CA67C8>, <weakref at 0x0000020667B6E2C8; to 'Student' at 0x0000020676CA6748>}
for ws in Student.stock_set:
ws.func() # 打印 XiaoMing XiaoWang
weak_one = Student.stock_set.pop() # pop 一个弱引用
weak_one.func() # 打印 XiaoMing
test() # 函数结束之后 两个 Student 实例对象成功被释放掉了
print(Student.stock_set.data) # 打印 set()
输出:
{<weakref at 0x0000020667B6E1D8; to 'Student' at 0x0000020676CA67C8>, <weakref at 0x0000020667B6E2C8; to 'Student' at 0x0000020676CA6748>}
XiaoWang
XiaoMing
XiaoWang
set()
我们看看这里使用 WeakValueDictionary():
import weakref
class Student:
stock = weakref.WeakValueDictionary()
def __new__(cls, *args, **kwargs):
new_one = super().__new__(cls) # 调用父类object初始化
cls.stock[args[0]] = new_one # 以 name 为键,值为实例对象的弱引用
return new_one
def __init__(self, name):
self.name = name
def func(self):
print(self.name)
def test():
s1, s2 = Student("XiaoMing"), Student("XiaoWang")
print(Student.stock.keys()) # Student.stock.keys() 实际上是一个生成器对象
print(sorted(Student.stock.keys())) # 使用sorted()输出
weak_xiaoming = Student.stock["XiaoMing"]
weak_xiaoming.func()
for k, v in Student.stock.items(): # k为name,v为弱引用
print(k, v)
# XiaoMing <__main__.Student object at 0x0000020676CA6748>
# XiaoWang <__main__.Student object at 0x0000020676CA67C8>
test() # 函数结束之后 两个 Student 实例对象成功被释放掉了
print(sorted(Student.stock.keys())) # 打印 []
输出:
<generator object WeakValueDictionary.keys at 0x00000206678FEF48>
['XiaoMing', 'XiaoWang']
XiaoMing
XiaoMing <__main__.Student object at 0x0000020676CA6748>
XiaoWang <__main__.Student object at 0x0000020676CA67C8>
[]
还有一个WeakKeyDictionary,其键是弱引用。(WeakKeyDictionary实例)可以为应用中其他部分拥有的对象附加数据,这样就无需为对象添加属性。这对覆盖属性访问权限的对象尤其有用。
import weakref
class Student:
def __init__(self, name):
self.name = name
def func(self):
print(self.name)
def __repr__(self):
return f'{Student}self.name'
s1, s2 = Student("XiaoMing"), Student("XiaoWang")
stock = weakref.WeakKeyDictionary()
stock[s1] = {"Class": "A1"}
stock[s2] = {"Class": "B2"}
print("Before del")
for i in stock.values():
print(i)
del s1, s2
print("After del")
for i in stock.values():
print(i)
Before del
{'Class': 'A1'}
{'Class': 'B2'}
After del