# 弱引用

# 弱引用概述

我们已知Python的垃圾回收机制是基于引用计数的。每次引用对象都会让对象的引用计数+1,当对象引用计数归0后,垃圾回收机制就择机将对象销毁。

但是在缓存等场景中,存在着弱引用的使用场景:引用对象但不阻碍对象的销毁,即不增加对象的引用计数。或者我们可以使用弱引用来避免循环引用的问题。

可以使用weakref.ref​获取对象的弱引用,当对象不存在时会返回None。

"""
>>> import weakref
>>> l = {1,2}  
>>> wref = weakref.ref(l)
>>> wref
<weakref at 0x000001B54726C8B8; to 'set' at 0x000001B547230908>
>>> wref()
{1, 2}
>>> l.add(3) 
>>> wref()   
{1, 2, 3}
>>> l = {4,5,6} 
>>> wref()  
{1, 2, 3} # 此处和下面都还能看到{1,2,3}和True的原因时控制台会自动把_变量指向上一条语句的执行结果,因此没有触发垃圾回收
>>> wref() is None
False # 
>>> wref() is None # 此时_指向了False,因此{1,2,3}被回收了
True
>>> wref() # None不会被打印
>>> 
"""

"""
>>> a = {1} 
>>> a
{1}
>>> _
{1}
"""

# 弱引用内建对象

weakref模块提供了WeakKeyDictionary和WeakValueDictionary类来创建一些特殊的字典,其特别之处是前者的键和后者的值都只是弱引用,当键不存在后会自动从字典中剔除,当值不存在后会变成None。

# 弱引用局限

由于弱引用是通过维护一个指向对象的指针(而不是跟踪对象的状态)来实现的,因此对于list​、dict​等存在动态扩缩容可能会导致内存地址变化的数据结构是无法使用弱引用的,但其子类可以轻松解决这个问题。

  • 可能会导致内存地址变化:因为扩缩容前会先尝试原地扩缩容,因此可能不会导致内存地址的变化

  • 本质上支持支持弱引用的对象必须是实现了__weakref__()​方法的类的实例。如果我们试图将一个不支持弱引用的对象作为弱引用对象,Python会抛出一个TypeError​异常。而内建的list​类并没有实现__weakref__()​方法,因此list​对象本身不能作为弱引用对象。

  • 而有的用户自建对象,会莫名奇妙多出一个__weakref__​方法,因此就可以用作弱引用目标了。

  • 不过对于int、tuple这两个对象的实例,其即使是子类也不可以作为弱引用目标。

    import weakref
    
    class MyInt(int):
        pass
    
    i1 = int(1)
    i2 = MyInt(2)
    # weakref.ref(i1)
    """
    Traceback (most recent call last):
      File "d:/Programs/Workspace/cpython-v3.7.4/PyTests/weakref_int.py", line 8, in <module>
        weakref.ref(i1)
    TypeError: cannot create weak reference to 'int' object
    """
    # weakref.ref(i2)
    """
    Traceback (most recent call last):
      File "d:/Programs/Workspace/cpython-v3.7.4/PyTests/weakref_int.py", line 8, in <module>
        weakref.ref(i1)
    TypeError: cannot create weak reference to 'int' object
    """
    
    print(MyInt.__mro__)        # (<class '__main__.MyInt'>, <class 'int'>, <class 'object'>)
    print("MRO:",[c for c in MyInt.__mro__ if "__weakref__" in c.__dict__])    # MRO: []
    
  • 上面这些局限是CPython的具体实现导致的,在不同解释器中具体情况可能完全不一致,因此没有细致研究。

  • 所以在使用弱引用的时候应该谨而慎之,谨而慎之。

import random
import weakref

ITEM_NUM = 10000000
ITEM_NUM = 0

class MyList(list):
    pass

l = MyList()
addr = set()
for i in range(ITEM_NUM):
    _i = random.randint(0,10**100)
    l.append(_i)
    if id(l) not in addr:
        print(f"ADDR CHANGE  {i}/{ITEM_NUM} {id(l)}")
        addr.add(id(l))
"""
ADDR CHANGE  0/10000000 2282120903800
"""

l = []
addr = set()
for i in range(ITEM_NUM):
    _i = random.randint(0,10**100)
    l.append(_i)
    if id(l) not in addr:
        print(f"ADDR CHANGE  {i}/{ITEM_NUM} {id(l)}")
        addr.add(id(l))
"""
ADDR CHANGE  0/10000000 2282093917000
"""

print(MyList.__mro__) # (<class '__main__.MyList'>, <class 'list'>, <class 'object'>)
print("MRO:",[c for c in MyList.__mro__ if "__weakref__" in c.__dict__])    # MRO: [<class '__main__.MyList'>]

# l1 = list()
# weakref.ref(l1)
"""
Traceback (most recent call last):
  File "d:/Programs/Workspace/cpython-v3.7.4/PyTests/weakref_list_test.py", line 38, in <module>
    weakref.ref(l1)
TypeError: cannot create weak reference to 'list' object
"""
l2 = MyList()
weakref.ref(l2)