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. 在原始对象被销毁时可以回调弱引用注册的回调函数

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

posted @ 2022-05-25 09:34  金色旭光  阅读(1053)  评论(0编辑  收藏  举报