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时就表明该对象没有被任何变量所引用,也就成为了内存垃圾,可以被垃圾回收机制所处理。
引用计数的特点:
- 当对象引用计数等于0时可以被回收;
- 当对象应用计数不等于0时不能被回收,除非触发手动回收;
弱引用
以上就是python中引用的基本知识,今天介绍的主角weakref
(弱引用)就是和引用的机制非常密切的模块。弱引用就是不产生引用计数的特殊引用。
特性
:
弱引用不会增加对象的引用数量。如果将引用的目标对象称为 指向对象(referent)。因此,弱引用不会妨碍所指对象被当作垃圾回收。
python中的弱引用会获取引用对象的地址,即可以调用对象对其进行相关操作,但是不会使引用的对象的引用计数增加,当引用对象的引用计数为0时,对象还是会被回收,弱引用也无法继续调用对象
- 弱应用可以操作指向对象的属性
- 弱应用不会增加指向对象的引用计数个数
适合场景
:
结论:弱引用在缓存应用中很有用
有这样一个场景,如果一个缓存的字典中保存了key为id,value为某大型对象这样的键值对。当大型对象被删除del object
之后,字典中保存的键值对依然不会被删除。因为字典存在,大型对象的引用计数会增加1。由于大型对象一直被引用,内存不能释放。
使用弱引用字典来保存如上的键值对,当大型对象删除时,缓存字典中的键值对也会被删除。能够有效释放内存。
weakref的使用
weakref.ref()使用
ref 的定义
class weakref.ref(object[, callback])
ref是用来构建弱引用最常见的函数,返回对对象的弱引用。
根据原始对象是否存活,返回值不同:
- 如果原始对象仍然存活,则可以通过调用引用对象来获得原始对象;
- 如果引用的原始对象不再存在,则调用引用对象将得到 None 。
- 支持传入回调函数,在原始对象即将终结时将调用回调。弱引用对象将作为回调的唯一参数传递
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>]
总结
总的来说弱引用有两个优点:
- 不占用引用计数,可节省内存
- 在原始对象被销毁时可以回调弱引用注册的回调函数
第一点在文中多处有强调,关于第二点是比较有特点的一个特性。可以使用第二点完成观察者模式,通知一些依赖某一个对象的所有对象。