weakref:对象的弱引用

介绍

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

引用

import weakref
 
 
'''
对象的弱引用要通过ref类来管理。要获取原对象,可以调用引用对象
'''
 
 
class RefObject:
    def __del__(self):
        print("del executed")
 
 
obj = RefObject()
# 创建弱引用
r = weakref.ref(obj)
 
print("obj:", obj)  # obj: <__main__.RefObject object at 0x0000000002964470>
# 显示关联RefObject
print("ref:", r)  # ref: <weakref at 0x000000000051BA48; to 'RefObject' at 0x0000000002964470>

# 引用r加上(),等价于obj,因此得到RefObject的实例对象
print("ref()", r())  # ref() <__main__.RefObject object at 0x0000000002964470>
 
# 删除obj执行析构函数
del obj  # del executed
# 之前说过调用r()等价于调用obj,但是obj被删除了,所以返回None
# 从这里返回None也能看出这个弱引用是不会增加引用计数的
print("r():", r())  # r(): None

引用回调

import weakref

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


class RefObject:
    def __del__(self):
        print("del executed")


def callback(reference):
    print(f"callback : {reference}")


obj = RefObject()
r = weakref.ref(obj, callback)
'''
当引用所引用的原对象"死亡"时,这个回调会接受这个引用对象作为参数。
这种特性的一种用法就是从缓存中删除弱引用对象。
'''

print("obj:", obj)  # obj: <__main__.RefObject object at 0x0000000002964630>
print("ref:", r)  # ref: <weakref at 0x0000000001D2BA48; to 'RefObject' at 0x0000000002964630>
print("ref()", r())  # ref() <__main__.RefObject object at 0x0000000002964630>

del obj  # 删除引用指向的对象
"""
del executed
callback : <weakref at 0x0000000001D2BA48; dead>   删除obj,执行回调,显示dead
"""
print("r():", r())  # r(): None

最终化对象

import weakref
 
 
'''
清理弱引用时要对资源完成更健壮的管理,可以使用finalize将回调与对象关联。
finalize实例会一直保留(直到所关联的对象被删除),即使没有保留最终化对象的引用
'''
 
 
class RefObj:
    def __del__(self):
        print("xxx")
 
 
def on_finalize(*args):
    print(f"on_finalize: {args}")
 
 
obj = RefObj()
weakref.finalize(obj, on_finalize, "callback的参数")
 
del obj
'''
xxx
on_finalize: ('callback的参数',)
'''
# finalize的参数包括要跟踪的对象,对象被垃圾回收时要调用的callback,以及参数(可以是位置参数,也可以是关键字参数)
 
 
# finalize实例对象还有一个atexit属性,用来控制程序退出时是否调用这个回调(如果还未调用)
obj1 = RefObj()
f = weakref.finalize(obj1, on_finalize, "callback的参数")
# 默认是调用回调,但是将atexit设置为False会禁用这种行为
f.atexit = False
'''
不会有任何的输出,注意:这里我虽然没有显示的删除obj1,但也能够说明结论
因为在f.atexit=True的情况下,即使不删除也依旧会执行callback。
原因是即使你不手动删除,但是对象已经被创建出来了,而程序结束的那一刻,也会执行析构函数的。因为对象总是要回收的,即使你不调用del,那么程序执行完毕的时候也会自动调用。
所以默认f.atexit = True是会打印的,但是现在没有打印,所以确实被禁用了
'''
import weakref
 
 
'''
如果向finalize实例提供一个跟踪对象的引用,这便会导致一个引用被保留,所以这个对象永远不会被垃圾回收
'''
 
 
class RefObj:
    def __del__(self):
        print("xxx")
 
 
def on_finalize(*args):
    print(f"on_finalize: {args}")
 
 
obj = RefObj()
obj_id = id(obj)
# 这里我将obj实例作为参数传进去了,这样的后果就是obj不会被回收,即使你删除了
f = weakref.finalize(obj, on_finalize, obj)
f.atexit = False
 
# 删除obj,让obj不再指向之前的对象 
del obj
import gc
# 获取所有的对象
for o in gc.get_objects():
    if id(o) == obj_id:
        # 结果发现真的没有被回收,因为引用不止obj一个,还有其它人在用
        print("found uncollected object in gc")  # found uncollected object in gc

代理

import weakref
 
 
'''
有时候使用代理比使用弱引用更方便。使用代理可以像使用原对象一样,而且不要求在访问对象之前先调用代理。
这说明,可以将代理传递到一个库,而这个库并不知道它接收的是一个代理而不是真正的一个对象。
'''
 
 
class RefObj:
 
    def __init__(self, name):
        self.name = name
 
    def __del__(self):
        print("xxx")
 
 
obj = RefObj("my obj")
r = weakref.ref(obj)
p = weakref.proxy(obj)
 
# 可以看到引用加上()才相当于原来的对象
# 而代理不需要,直接和原来的对象保持一致
print("via obj:", obj.name)  # via obj: my obj
print("via ref:", r().name)  # via ref: my obj
print("via proxy:", p.name)  # via proxy: my obj
 
del obj  # xxx
try:
    # 删除对象之后,再调用引用,打印为None
    print(r())  # None
    # 但是如果调用代理的话,则会抛出一个ReferenceError
    print(p)
except Exception as e:
    print(e)  # weakly-referenced object no longer exists

自定义类指定弱引用

当我们自定义一个类的时候,如果为了省内存,那么会不使用__dict__属性,因为每一个类或者实例都会有一个自己的属性字典__dict__,而我们知道字典使用的是哈希表,这是一个使用空间换时间的数据结构,因此如果想省内存的话,那么我们通常的做法是指定__slots__属性,这样就不会再有__dict__属性了。

class A:
    __slots__ = ('name', 'age')

    def __init__(self, name, age):
        # 此时在__init__里面,只能有self.name和self.age
        # 这是因为我们在__slots__里面只指定了name和age
        # 因此当我们需要省内存、并且属性固定的时候,可以指定__slots__属性
        self.name = name
        self.age = age

    def __str__(self):
        return f"name is {self.name}, age is {self.age}"


if __name__ == '__main__':
    import weakref
    a = A("hanser", 27)
    try:
        r = weakref.proxy(a)
    except TypeError as e:
        print(e)  # cannot create weak reference to 'A' object

但是我们发现此时这个A的实例对象是没有办法被弱引用的,因为我们指定了__slots__,那么要怎么做呢?直接在__slots__里面加上一个属性就好了。

class A:
    # 多指定一个__weakref__,表示支持弱引用
    __slots__ = ('name', 'age', '__weakref__')

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"name is {self.name}, age is {self.age}"


if __name__ == '__main__':
    import weakref
    a = A("hanser", 27)
    r = weakref.proxy(a)
    print(r)

可以看到此时就支持弱引用了。

原文地址:https://www.cnblogs.com/traditional/p/11870281.html