Loading

Python weakref 弱引用

Python weakref 弱引用

要理解weakref核心在于Python的垃圾回收机制及其包含的引用计数

引用计数

import sys

a = "cnblogs.sgt"
# 使用 sys.getrefcount 查看变量引用计数
# 注意下:函数 sys.getrefcount() 调用时会增加一个引用,调用完又销毁一个引用。 实际上就是传参的问题
# 所以这里a的引用计数不出意外因该是2,如果你使用的是三方IDE,可能会更多,建议使用命令行查看
a_ref_count = sys.getrefcount(a)	# 结果为2
# 定义一个变量a,a的值存入内存,它被打上标识a的标签
# 此时存入内存地址的1这个值便有了一个引用,其引用计数+1
b = a
a_ref_count = sys.getrefcount(a)	# 结果为3
# 当我将a的值赋值给b这个变量时候,原来1这个值存入内存的标签则多了一个b,此时存入内存地址的a的值便多了一个引用,其引用计数+1
a_list = []
a_list.append(a)
a_ref_count = sys.getrefcount(a)	# 结果为4

引用计数增加

  1. 创建对象,用变量接收 : name = '123'
  2. 对象被其他的变量引用 : name2 = name
  3. 对象被其他的对象引用(放入其他容器类型的数据中)li = [11, 22, name]
  4. 当成函数传入函数中时
    def func(n):
    print(n)
    func(name)

引用计数减少

  1. 变量被显式销毁 del name
  2. 引用对象的变量指向了其他对象(变量被重新赋了个别的值) name2 = ‘abc’
  3. 从其他对象(容器类型数据中移除时),li.pop()
  4. 函数执行完毕之后,内部作用域的变量会被销毁

Python数据池机制

  1. 小整数池: -5 到 256之间的整数,python做了一个数据池实现创建好所有的数据,当程序中使用这个范围内的对象,不会重新创建,直接引用
  2. 字符串驻留池intern机制:由纯单词字符(数字、字母、下划线)组成的字符串,python也做了优化纯单字符组成的字符串,在创建之后会被加入字符串驻留池中,如果程序中再次定义相同的字符串,则直接引用

重点来了:以上所说的引用都是强引用(相对于弱引用而言)

弱引用

相对于强引用时候增加引用会增加引用计数,弱引用也会增加引用,但是引用计数不变

弱引用不会增加对象的引用数量。引用的目标对象称为 所指对象 (referent)。因此,弱引用不会妨碍所指对象被当作垃圾回收。

弱引用在缓存应用中很有用,因为不想仅因为被缓存引用着而始终保存缓存对象。

使用 weakref.ref 实例可以获取所指对象。如果对象存在,调用弱引用可以获取对象;否则返回 None

weakref.ref 类其实是低层接口,供高级用途使用,多数程序最好使用 weakref 工具集finalize

weakref 工具集合:

  • WeakKeyDictionary:
  • WeakValueDictionary: 这是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从 WeakValueDictionary 中删除。因此,WeakValueDictionary 经常用于缓存。
  • WeakSet: 保存元素弱引用的集合类。元素没有强引用时,集合会把它删除。
  • finalize (内部使用弱引用)

如果一个类需要知道所有实例,一种好的方案是创建一个 WeakSet 类型的类属性,保存实例的引用。

如果使用常规的 set ,实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与 Python 进程一样长,除非显式删除类。

弱引用的局限性可参考官方文档

# 前提: Python 控制台会自动把 _ 变量绑定到结果不为 None 的表达式结果上。
In [1]: import weakref
In [2]: a_set = {0,1}
In [3]: wref = weakref.ref(a_set)  # 弱引用对象 wref
In [4]: wref
Out[4]: <weakref at 0x10f29aea8; to 'set' at 0x10e906f28>
In [5]: wref()  # 返回的是被引用的对象,{0, 1}。因为是控制台会话,所以 {0, 1} 会绑定给 _ 变量。
Out[5]: {0, 1}
In [6]: a_set = {2, 3, 4}   # a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _ 变量仍然指代它。
In [7]: wref()
Out[7]: {0, 1}
In [8]: wref() is None  # 计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。但是,随后 _ 绑定到结果值 False。现在 {0, 1} 没有强引用了。
Out[8]: False
In [9]: wref() is None  # 因为 {0, 1} 对象不存在了,所以 wref() 返回 None。
Out[9]: Ture  

WeakValueDictionary 示例:

class Cheese:
    def __init__(self, kind):
        self.kind = kind

    def __repr__(self):
        return 'Cheese(%r)' % self.kind
# 执行:
In [2]: import weakref
In [3]: stock = weakref.WeakValueDictionary()  # 创建弱引用字典实例。
In [4]: catalog = [Cheese('Read Leicester'), Cheese('Tilsit'),Cheese('Brie'), Cheese('Parmesan')]
In [6]: for cheese in catalog:
   ...:     stock[cheese.kind] = cheese  # 名称映射到实例. [弱引用]
   ...:
In [7]: sorted(stock.keys())
Out[7]: ['Brie', 'Parmesan', 'Read Leicester', 'Tilsit']
In [8]: del catalog
In [9]: sorted(stock.keys())  # 为什么还剩一个? 因为临时变量。
Out[9]: ['Parmesan']
In [10]: del cheese
In [11]: sorted(stock.keys())  # 临时变量删除后,为空.
Out[11]: []
>>> import weakref
>>>
>>> class C:                            # 这里新建一个类,因为WeakValueDictionary()
...     def __init__(self, value):      # 要求value是一个obj
...             self.value = value
...
>>> def test_weak_value_dict():
...     d= weakref.WeakValueDictionary()
...     k1 = 'test1'                    
...     v1 = C(1)                       # 这时候C(1)是有一个强引用的:v1
...     d[k1] = v1                      # 这个语句也就是字典赋值,但是由于我们用的
...     print(d[k1])                    # WeakValueDictionary(),所以字典里的是弱引用
...     del v1                          # 这时候删除了C(1)唯一的强引用 v1,因此
...     print(d[k1])                    # WeakValueDictionary()里面 k1,v1 这个键值对消失了
...
>>> test_weak_value_dict()
<__main__.C object at 0x000001793E545198>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in test_weak_value_dict
  File "C:\Users\ASUS\Anaconda3\lib\weakref.py", line 137, in __getitem__
    o = self.data[key]()
KeyError: 'test1'
>>>

weakref.ref()weakref.proxy() 的区别

我们在使用weak.ref时,返回值ref,需要执行ref()才是弱引用的对象,ref() 相当于 c_obj

weakref.proxy的返回值直接就是弱引用的对象,返回值proxy直接相当于c_obj

posted @ 2023-06-16 07:54  MrSu  阅读(160)  评论(0编辑  收藏  举报