207. python弱引用

1.weakref弱引用对象:

我大概说一下我的理解: 若对象就是强引用对象的一个指针引用, 但是弱引用对象不增加应用计数(也就是说弱应用不影响垃圾回收), 当强引用对象被删除时, 弱引用对象自动删除.

比如: 你的大名和你的小名一样, 如果你挂了, 就都是去了意义(而且社会承认的是你的大名, 你的小明只有个别人知道, 通过大名将你人道消除, 小明也没了)

https://docs.python.org/zh-cn/3/library/weakref.html#weakref.WeakMethod

弱引用对象没有 ref.__callback__ 以外的方法和属性。 一个弱引用对象如果存在,就允许通过调用它来获取引用:
import weakref
class Object:
    pass

o = Object()
r = weakref.ref(o)  # 可以理解为一个装饰器
o2 = r()  # 调用r时返回被装饰得对象
print(o is o2)  # True
del o, o2
print(r()) # None
检测一个弱引用对象是否仍然存在应该使用表达式 ref() is not None。 通常,需要使用引用对象的应用代码应当遵循这样的模式:
o = r()
if o is None:
    print("弱引用对象已被回收, 不可调用")
else:
    print("弱引用对象存活")
    o.do_something_useful()
# 根据官网翻译, 他说这个东西竟然是线程安全的
使用单独的“存活”测试会在多线程应用中制造竞争条件;其他线程可能导致某个弱引用在该弱引用被调用前就失效;上述的写法在多线程应用和单线程应用中都是安全的。

特别版本的 ref 对象可以通过子类化来创建。 在 WeakValueDictionary 的实现中就使用了这种方式来减少映射中每个条目的内存开销。 这对于将附加信息关联到引用的情况最为适用,但也可以被用于在调用中插入额外处理来提取引用。
----------------------------------------------------------------------------------
我把官网得了例子改了一下, 继承改为weakref.WeakMethod
import weakref
class ExtendedRef(weakref.WeakMethod):
    def __init__(self, ob, callback=None, **annotations):
        print(ob.__self__, ob.__func__)  # 看源码的时候这两个分别表示ob所在类的实例化对象和ob方法本身
        super().__init__(ob, callback)
        self.__counter = 0
        for k, v in annotations.items():
            setattr(self, k, v)

    def __call__(self):
        """Return a pair containing the referent and the number of
        times the reference has been called.
        """
        ob = super().__call__()
        if ob is not None:
            self.__counter += 1
            ob = (ob, self.__counter)
        return ob


class C:
    def print(self):
        print("11111")


c = C()
print(c)
print(c.print.__func__)
r = ExtendedRef(c.print, callback=None)
ans = r()
print(ans[0](), ans[1])
----------------------------------------------------------------------------------
这个简单的例子演示了一个应用如何使用对象 ID 来提取之前出现过的对象。 然后对象的 ID 可以在其它数据结构中使用,而无须强制对象保持存活,但处于存活状态的对象也仍然可以通过 ID 来提取。
# 我感觉这个例子主要是为了给你展示, 弱引用是咋么实现的, id可以不存在, 不存在可以返回None
import weakref

_id2obj_dict = weakref.WeakValueDictionary()

def remember(obj):
    oid = id(obj)
    _id2obj_dict[oid] = obj
    return oid

def id2obj(oid):
    return _id2obj_dict[oid]

def func():
    return "111111"

oid = remember(func)
print(_id2obj_dict.valuerefs()[0]()())  # _id2obj_dict.valuerefs()存放的是一个函数, 这个函数返回另外一函数(和闭包很像)
print([value for value in _id2obj_dict.keys()])  #  == print(id(func))
print(id2obj(oid)())
----------------------------------------------------------------------------------

2.finalize终结器对象:

它和软引用不同的地方在于

使用 finalize 的主要好处在于它能更简便地注册回调函数,而无须保留所返回的终结器对象。 例如
class Object:
    pass

kenny = Object()
weakref.finalize(kenny, print, "you kinded kenny")
del kenny  # 对象销毁是调用
----------------------------------------------------------------------------
终结器也可以被直接调用。 但是终结器最多只能对回调函数发起一次调用。
class Object:
    pass
def callback(x, y, z):
    print("CALLBACK")
    return x + y + z
obj = Object()
f = weakref.finalize(obj, callback, 1, 2, z=3)
assert f.alive
assert f() == 6  # 这里调用过后f.alive会被设置为False,
assert not f.alive
f()  # 这个调用没效果, f.alive=False了
del obj  # 这个调用没效果, f.alive=False了
----------------------------------------------------------------------------
你可以使用 detach() 方法来注销一个终结器。 该方法将销毁终结器并返回其被创建时传给构造器的参数。
class Object:
    pass
def callback(x, y, z):
    print("CALLBACK")
    return x + y + z
obj = Object()
f = weakref.finalize(obj, callback, 1, 2, z=3)
newobj , func, args, kwargs = f.detach()
assert not f.alive
assert newobj is obj
assert func(*args, **kwargs) == 6
----------------------------------------------------------------------------
除非你将 atexit 属性设为 False,否则终结器在程序退出时如果仍然存活就将被调用。 例如
import weakref
class Object:
    pass
obj = Object()
weakref.finalize(obj, print, "obj dead or exiting")
exit()  # obj dead or exiting

3.比较终结器与 __del__() 方法:

假设我们想创建一个类,用它的实例来代表临时目录。 当以下事件中的某一个发生时,这个目录应当与其内容一起被删除:

  • 对象被作为垃圾回收,

  • 对象的 remove() 方法被调用,或

  • 程序退出。

我们可以尝试使用 __del__() 方法来实现这个类,如下所示:

import tempfile
import shutil
class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()

    def remove(self):
        if self.name is not None:
            shutil.rmtree(self.name)
            self.name = None

    @property
    def removed(self):
        return self.name is None

    def __del__(self):
        self.remove()

从 Python 3.4 开始,del() 方法不会再阻止循环引用被作为垃圾回收,并且模块全局变量在 interpreter shutdown 期间不会被强制设为 None。 因此这段代码在 CPython 上应该会正常运行而不会出现任何问题。

然而,del() 方法的处理会严重地受到具体实现的影响,因为它依赖于解释器垃圾回收实现方式的内部细节。
更健壮的替代方式可以是定义一个终结器,只引用它所需要的特定函数和对象,而不是获取对整个对象状态的访问权:

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()
        # 使用一个终结期对象, 不需要保存原本弱引用对象的返回, 只需要注册好回调函数
        self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)

    def remove(self):
        self._finalizer()  # 调用之后自动触发回调

    @property
    def removed(self):
        return not self._finalizer.alive

像这样定义后,我们的终结器将只接受一个对其完成正确清理目录任务所需细节的引用。 如果对象一直未被作为垃圾回收,终结器仍会在退出时被调用。

基于弱引用的终结器还具有另一项优势,就是它们可被用来为定义由第三方控制的类注册终结器,例如当一个模块被卸载时运行特定代码:

import weakref, sys
def unloading_module():
    # implicit reference to the module globals from the function body
weakref.finalize(sys.modules[__name__], unloading_module)

注解

如果当程序退出时你恰好在守护线程中创建终结器对象,则有可能该终结器不会在退出时被调用。 但是,在一个守护线程中 atexit.register(), try: ... finally: ...with: ... 同样不能保证执行清理。

总结:

弱应用对象既就是对象的一个软连接, 不对解释器垃圾回收产生影响, 当该对象的强引用都被解除之后, 针对弱引用对象垃圾回收器可以直接.weakref. finalize与weakref.ref类似, 不过finalize可以注册回调函数, (回调函数在退出程序或者注册在其中的强引用对象被删除是执行). 在我们需要清理回收某些资源, 或者在某写对象销毁时执行某些操作,可以使用finalize.

posted @ 2021-12-01 11:24  楠海  阅读(106)  评论(0)    收藏  举报