Python--weakref

weakref - Weak references

在了解弱引用之前,需要弄明白 python 中的 garbage collection (下面简称 gc)。

python中内存管理和垃圾回收主要有两个:

  • 引用计数
  • 分代垃圾回收

引用计数

和其他高级语言的引用计数一样:当某个对象被引用一次,计数加一;取消引用一次,计数减一;当计数等于零时,就会将该对象的内存释放了。
有很多增加引用计数的方法,例如:

  • 为一个对象赋值
  • 将一个对象添加到数据结构中,例如,append到列表中
  • 某个对象作为参数被调用
    实例:
    1
    2
    3
    4
    5
    >>> import sys
    >>> obj = [] # 这里不要使用字符串或者数字,因为python做了缓存,结果会比预期大。
    >>> ll = [obj]
    >>> sys.getrefcount(obj)
    3

分代垃圾回收

为什么有了引用计数,还需要这个所谓的分代垃圾回收呢?试想一下,如果有一个对象引用了它自己,就算我们使用del删除了这个对象,它的计数也不会变成0,因为它还有一个指向自己的引用。这种情况称为循环引用。这里请注意!!!(敲黑板了)解决循环引用的方式是标记清除方法,这里就不介绍了,详情看这里

python中的分代回收主要有两个核心概念:

  • 首先就是分代中的。垃圾回收会追踪内存中的所有对象。一个新的对象会在垃圾回收器的第一代开始其生命。如果python对某一代执行垃圾回收,如果某个对象存活下来它将会被移动到下一代。python的垃圾回收器共有三代,一个对象只要在当代的垃圾回收中存活下来,他就会移动到下一代。
  • 第二个概念就是阈值。在每一代中,垃圾回收器都会有一个对象数量的阈值。如果对象的数量超过了阈值,垃圾回收器就会触发一次回收,任何在回收过程中存活下来的对象都会被移动到下一代中。

那么分代垃圾回收和标记清除方法有什么关联了,简单来说,标记清除方法会暂停程序,比较耗时;而分代垃圾回收其实使用空间换时间,提高了回收效率。具体后续我继续写,这里先占一个坑,手动狗头。

weakref

从前面的内容中我们知道了简单但是高效的引用计数不能解决循环引用的问题,但是分代回收用比较复杂,较为耗时。试想一下,一个对象被引用了,却又不会增加引用计数,那么这个对象是不是就会被高效的释放。下面是一个具体的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import weakref


cache = {}
cache_ref = weakref.WeakValueDictionary()

class Spam:
def __init__(self, name):
self.name = name

s1 = Spam('s1')
s2 = Spam('s2')

cache['s1'] = s1
cache_ref['s2'] = s2

del s1 # memory will not be freed
del s2 # memory will be freed

可以看出在做缓存时,弱引用会很有用,特别针对大对象储存。

总结

  • python中的垃圾回收主要以引用计数为主,分代回收为辅,使用标记清除解决循环引用的问题。
  • 弱引用可以用来解决循环引用的问题,在对于大对象高速缓存也可以大展身手。