python的 垃圾回收和弱引用

del和垃圾回收

del 语句删除名称,而不是对象。 del 命令可能会导致对象被当作垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时。

有个 __del__ 特殊方法,但是它不会销毁实例,不应该在代码中调用。即将销毁实例时, Python 解释器会调用 __del__ 方法,给实例最后的机会,释放外部资源。

在 CPython 中,垃圾回收使用的主要算法是引用计数。每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁;CPython 会在对象上调用__del__ 方法(如果定义了),然后释放分配给对象的内存。

CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组——如果一组对象之间全是相互引用即使再出色的引用方式也会导致组中的对象不可获取。

监视对象生命结束时的情形

为了演示对象生命结束时的情形,使用 weakref.finalize 注册一个回调函数,在销毁对象时调用。

import weakref

s1 = {1, 2, 3}

s2 = s1

def bye():

    print('gone ...')

ender = weakref.finalize(s1, bye)

print(ender.alive)  # True

del s1

print(ender.alive)  # True

s2 = 'spam'

# gone ...

print(ender.alive)  # False

del 不删除对象,而是删除对象的引用。重新绑定最后一个引用 s2,让 {1, 2, 3} 无法获取。对象被销毁了,调用了 bye 回调, ender.alive 的值变成了 False。

弱引用

你可能觉得奇怪,为什么上面的 {1, 2, 3} 对象被销毁了?毕竟,我们把 s1 引用传给 finalize 函数了,而为了监控对象和调用回调,必须要有引用。这是因

为, finalize 持有 {1, 2, 3} 的弱引用。

正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。

弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。

弱引用是可调用的对象,返回的是被引用的对象;如果所指对象不存在了,返回 None:

import weakref

s1 = {1, 2, 3}

wref = weakref.ref(s1)

# <weakref at 0x7f96e8f2b9a8; to 'set' at 0x7f96e8f1b208> {1, 2, 3}

print(wref, wref())

s1 = {2}

# <weakref at 0x7f96e8f2b9a8; dead> None

print(wref, wref())

注:weakref.ref类其实是低层接口,供高级用途使用,多数程序最好使用 weakref 集合和 finalize。也就是说,应该使用 WeakKeyDictionary、 WeakValueDictionary、 WeakSet 和finalize(在内部使用弱引用),不要自己动手创建并处理 weakref.ref 实例。

WeakValueDictionary、WeakKeyDictionary

WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从 WeakValueDictionary中删除。因此, WeakValueDictionary 经常用于缓存。

import weakref

a = {1, 2, 3}

b = {4, 6}

data = weakref.WeakValueDictionary()

data['a'] = a

data['b'] = b

print(data.get('a'))  # {1, 2, 3}

del a

print(data.get('a'))  # None

上面因为删掉了变量a,所以{1, 2, 3}对象因为没有了引用而被销毁,所以第二次data.get('a')就不能取到值了。

与 WeakValueDictionary 对应的是 WeakKeyDictionary,后者的键是弱引用。

WeakSet

这个类的作用很简单: 保存元素弱引用的集合类。元素没有强引用时,集合会把它删除。

如果一个类需要知道所有实例,一种好的方案是创建一个 WeakSet 类型的类属性,保存实例的引用。如果使用常规的 set,实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与 Python进程一样长,除非显式删除类。

弱引用set:

import weakref

class Cheese:

    def __init__(self, kind):

        self.kind = kind

    def __repr__(self):

        return 'Cheese(%r)' % self.kind

c1, c2 = Cheese('zhe'), Cheese('xiao')

ins_set = weakref.WeakSet()

ins_set.add(c1)

ins_set.add(c2)

# [Cheese('xiao'), Cheese('zhe')]

print(list(ins_set))

del c1

# [Cheese('xiao')]

print(list(ins_set))

常规set:

normal_set = set()

normal_set.add(c1)

normal_set.add(c2)

# [Cheese('xiao'), Cheese('zhe')]

print(list(normal_set))

del c1

# [Cheese('xiao'), Cheese('zhe')]

print(list(normal_set))

弱引用的局限

不是每个 Python 对象都可以作为弱引用的目标。

int 和 tuple 实例不能作为弱引用的目标,甚至它们的子类也不行。

list 和 dict实例不能作为所指对象,但是它们的子类可以。

import weakref

a = [1,2,3]

data = weakref.WeakValueDictionary()

# TypeError: cannot create weak reference to 'list' object

data['a'] = a

上面的操作会直接报错,我们需要实现list子类:

import weakref

class Mylist(list):

    pass

a = Mylist([1,2,3])

data = weakref.WeakValueDictionary()

data['a'] = a

set 实例可以作为弱引用所指对象,用户定义的类型也没问题。

Python对不可变类型施加的把戏 驻留(interning)

t1 = (1, 2, 3)

t2 = tuple(t1)

t3 = t1[:]

# 139702473690352 139702473690352 139702473690352

print(id(t1), id(t2), id(t3))

可以发现,对元组 t1 来说, t1[:] 不创建副本,而是返回同一个对象的引用。此外, tuple(t1) 获得的也是同一个元组的引用。

t1 = (1, 2, 3)

t2 = (1, 2, 3)

# 140559057587440 140559057221096

print(id(t1), id(t2))

s1 = "zhexiao"

s2 = "zhexiao"

# 140304044967840 140304044967840

print(id(s1), id(s2))

t1 和 t2 相等,但不是同一个对象。但是s1 和 s2 指代同一个对象。这种技术叫共享字符串字面量,是一种优化措施,称为驻留(interning)。

CPython 还会在小的整数上使用这个优化措施,防止重复创建“热门”数字,如 0、 -1 和 42。注意, CPython 不会驻留所有字符串和整数,驻留的条件是实现细节,而且没有文档说明。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值