• Python中节省内存的方法之二:弱引用weakref


    弱引用和引用计数息息相关,在介绍弱引用之前首先简单介绍一下引用计数。

    引用计数

    Python语言有垃圾自动回收机制,所谓垃圾就是没有被引用的对象。垃圾回收主要使用引用计数来标记清除。
    引用计数:python中变量名和数据之间使用引用来建立联系。如a = [1,2,3]。列表[1,2,3]被变量a所引用,所以列表[1,2,3]的引用计数就是1。python中每一个对象都有引用计数。

    可以通过sys模块的getrefcount获取某一个对象被引用计数的个数

    >>> a = [1,2,3]
    >>> 
    >>> import sys
    >>> sys.getrefcount(a) # 由于方法本身也引用了变量a,所以个数为2。
    2
    >>> 
    >>> b = a
    >>> 
    >>> sys.getrefcount(a)
    3
    >>> 
    

    b=a相当于新创建一个变量b指向[1,2,3]

    垃圾回收:当某一个对象的引用计数等于0时就表明该对象没有被任何变量所引用,也就成为了内存垃圾,可以被垃圾回收机制所处理。

    引用计数的特点:

    1. 当对象引用计数等于0时可以被回收;
    2. 当对象应用计数不等于0时不能被回收,除非触发手动回收;

    弱引用

    以上就是python中引用的基本知识,今天介绍的主角weakref(弱引用)就是和引用的机制非常密切的模块。弱引用就是不产生引用计数的特殊引用。

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

    python中的弱引用会获取引用对象的地址,即可以调用对象对其进行相关操作,但是不会使引用的对象的引用计数增加,当引用对象的引用计数为0时,对象还是会被回收,弱引用也无法继续调用对象

    1. 弱应用可以操作指向对象的属性
    2. 弱应用不会增加指向对象的引用计数个数

    适合场景
    结论:弱引用在缓存应用中很有用
    有这样一个场景,如果一个缓存的字典中保存了key为id,value为某大型对象这样的键值对。当大型对象被删除del object之后,字典中保存的键值对依然不会被删除。因为字典存在,大型对象的引用计数会增加1。由于大型对象一直被引用,内存不能释放。
    使用弱引用字典来保存如上的键值对,当大型对象删除时,缓存字典中的键值对也会被删除。能够有效释放内存。

    weakref的使用

    weakref.ref()使用

    ref 的定义

    class weakref.ref(object[, callback])
    

    ref是用来构建弱引用最常见的函数,返回对对象的弱引用。
    根据原始对象是否存活,返回值不同:

    1. 如果原始对象仍然存活,则可以通过调用引用对象来获得原始对象;
    2. 如果引用的原始对象不再存在,则调用引用对象将得到 None 。
    3. 支持传入回调函数,在原始对象即将终结时将调用回调。弱引用对象将作为回调的唯一参数传递

    demo

    import sys
    import weakref
    
    
    class Demo():
        
        def __init__(self, value):
            self.a = value
    
        def get_value(self):
            print(self.a)
    
    
    demo = Demo(100)
    demo_weakref = weakref.ref(demo) # 创建弱引用对象
    
    print(demo_weakref()) # 通过调用函数的方法来调用,返回的是原始对象 
    >>> <__main__.Demo object at 0x7f21840e2e80>
    
    print(demo_weakref() is demo) # 可以看出弱应用对象指向原始对象 
    >>> True
    
    demo_weakref().get_value() # 调用原始对象的方法
    >>> 100
    
    del demo 
    print(demo_weakref()) # 删除原始对象之后,弱引用对象返回None
    >>> None
    

    弱应用对象不增加引用计数

    import sys
    import weakref
    
    
    class Demo():
        
        def __init__(self, value):
            self.a = value
    
        def get_value(self):
            print(self.a)
    
    
    demo = Demo(100)
    print(sys.getrefcount(demo))
    >>> 2
    demo_weakref = weakref.ref(demo)
    print(sys.getrefcount(demo))
    >>> 2
    

    注册函数的使用

    通过ref构建弱引用对象时,可以传入回调函数,在原始对象销销毁时回调函数被调用。
    需要注意回调函数的参数一定要传入弱引用对象

    import sys
    import weakref
    
    
    class Demo():
        
        def __init__(self, value):
            self.a = value
    
        def get_value(self):
            print(self.a)
    
    def notify_by_delete(weakref_obj):
        print(f"{weakref_obj}注意:引用的对象被删除了")
    
    
    demo = Demo(100)
    demo_weakref = weakref.ref(demo, notify_by_delete)
    demo_weakref().get_value()
    del demo
    

    weakref.proxy 的使用

    ref在使用时需要显示调用才能获得原始对象,使用proxy返回原始对象的代理,使用代理对象可直接访问原始对象。

    定义

    weakref.proxy(object[, callback])
    

    demo

    import sys
    import weakref
    
    
    class Demo():
        
        def __init__(self, value):
            self.a = value
    
        def get_value(self):
            print(self.a)
    
    
    demo = Demo(100)
    demo_weakref = weakref.proxy(demo)
    
    demo.get_value()
    >>> 100
    
    demo_weakref.get_value()
    >>> 100
    

    proxy相比ref省去了函数调用这一步,可以说使用更加方便。

    WeakKeyDictionary

    WeakKeyDictionary 是以弱引用对象为key的字典。
    优点:创建一个key为弱引用的字典。优点是当key不在有引用计数时,key-value的映射会在字典中消失。

    定义

    weakref.WeakKeyDictionary([dict])
    

    普通字典

    在普通字典中,如果key是一个变量名,那么当变量被删除之后,字典中的key不会被删除。所以在一些场景中,如果是以对象作为key,那么删除对象之后需要字典中的key-value也能被删除,就可以使用弱引用对象。

    import sys
    import weakref
    
    class Demo():
        
        def __init__(self, value):
            self.a = value
    
        def get_value(self):
            print(self.a)
    
    Dict = {}
    
    a = 100
    Dict[a] = "一百"
    print(Dict)
    >>> {100: '一百'}
    
    del a 
    print(Dict)
    >>> {100: '一百'}
    

    当变量a被删除之后,字典Dict中的key并不受影响。

    弱引用字典 demo

    wkdict = weakref.WeakKeyDictionary()
    
    demo = Demo(200)
    wkdict[demo] = "二百"
    print(list(wkdict.items()))
    
    del demo
    print(list(wkdict.items()))
    
    >>>
    [(<__main__.Demo object at 0x10458b3a0>, '二百')]
    []
    

    在弱引用字典中,key是一个对象,如果对象被删除之后,弱引用字典中的key-value键值对也会被删除。

    以弱引用对象为value的字典 WeakValueDictionary

    使用弱引用作为value的映射类:当不再有对value的强引用时,将丢弃字典中的条目。与weakref.WeakKeyDictionary功能类似,只不过作为弱引用的是value。功能类似,不再话下。

    weakref.finalize

    finalize 主要用来标志原始对象的销毁。finalize构建时传入一个函数,当原始对象被删除时会自动调用这个函数。
    在原始对象被删除之前也可以手动调用finalize对象,但是其最多可以被调用一次,再次调用不会调用注册函数。

    import sys
    import weakref
    
    
    class Demo():
        
        def __init__(self, value):
            self.a = value
    
        def get_value(self):
            print(self.a)
    
    demo = Demo(100)
    
    def delete_exec(x):
        print('demo 被回收了..', "传入参数:", x)
    
    res = weakref.finalize(demo, delete_exec, 200)
    del demo
    

    获取弱引用统计

    想要获取一个对象的弱引用情况,可以通过getweakrefcount获取弱引用个数,getweakrefs获取弱引用的列表。

    import sys
    import weakref
    
    
    class Demo():
        
        def __init__(self, value):
            self.a = value
    
        def get_value(self):
            print(self.a)
    
    
    demo = Demo(100)
    demo_weakref_one = weakref.ref(demo)
    demo_weakref_two = weakref.proxy(demo)
    
    
    count = weakref.getweakrefcount(demo)
    print(count)
    >>> 2
    
    weak_list = weakref.getweakrefs(demo)
    print(weak_list)
    >>> [<weakref at 0x7f2e27c1d458; to 'Demo' at 0x7f2e27be0e80>, <weakproxy at 0x7f2e27b652c8 to Demo at 0x7f2e27be0e80>]
    
    

    总结

    总的来说弱引用有两个优点:

    1. 不占用引用计数,可节省内存
    2. 在原始对象被销毁时可以回调弱引用注册的回调函数

    第一点在文中多处有强调,关于第二点是比较有特点的一个特性。可以使用第二点完成观察者模式,通知一些依赖某一个对象的所有对象。

  • 相关阅读:
    ActiveReports中如何在后台导出运行时绑定数据源报表
    ActiveReports中如何控制页面的记录数
    WordPress建站固定链接问题
    Linux环境下使用g++编译C++
    Git diff结果显示分析
    VTK使用过程中遇到的问题
    右值引用、移动语义和完美转发(下)
    右值引用、移动语义和完美转发(中)
    右值引用、移动语义和完美转发(上)
    new和malloc的联系与区别(下)
  • 原文地址:https://www.cnblogs.com/goldsunshine/p/16307598.html
Copyright © 2020-2023  润新知