第8章:对象引用、可变性和垃圾回收-del、垃圾回收、弱引用

8.5 del 和垃圾回收 

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

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

CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组——如果一组对象之间全是相互引用,即使再出色的引用方式也会导致组中的对象不可获取。Python 的其他实现有更复杂的垃圾回收程序,而且不依赖引用计数,这意味着,对象的引用数量为零时可能不会立即调用 __del__ 方法。

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

import weakref


# 1. s1 和 s2 是别名,指向同一个集合,{1, 2, 3}。
s1 = {1, 2, 3}
s2 = s1


# 2. 这个函数一定不能是要销毁的对象的绑定方法,否则会有一个指向对象的引用。
def bye():
    print('Gone ... with the wind...')


# 3. 在 s1 引用的对象上注册 bye 回调。
ender = weakref.finalize(s1, bye)

# 4. 调用 finalize 对象之前,.alive 属性的值为 True。
print(ender.alive)  # True

# 5. del 不删除对象,而是删除对象的引用。
del s1
print(ender.alive)  # True

# 6. 重新绑定最后一个引用 s2,让 {1, 2, 3} 无法获取。
# 对象被销毁了,调用了 bye 回调 ender.alive 的值变成了 False。
s2 = 'spam'  # Gone with the wind...
print(ender.alive)  # False

示例的目的是明确指出 del 不会删除对象,但是执行 del 操作后可能会导致对象不可获取,从而被删除。

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

8.6 弱引用

正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后, 垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间(例如缓存技术),这就用到了弱引用。

弱引用不会增加对象的引用数量。引用的目标对象称为所指对象 (referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。

示例 8-17 弱引用是可调用的对象,返回的是被引用的对象;如果所指对象不存在了,返回 None

⚠️注意:在控制台中,Python 会自动把 _ 变量绑定到结果不为 None 的表达式结果上,这种不明显的隐式赋值会为对象创建新引用。

>>> import weakref
>>> a_set = {0, 1}

# 1. 创建弱引用对象 wref
>>> wref = weakref.ref(a_set)

# 2. 调用 wref() 返回的是被引用的对象,{0, 1}。
# 因为这是控制台会话,所以 {0, 1} 会绑定给 _ 变量。
>>> wref()
{0, 1}
>>> _
{0, 1}

# 3. a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。
# 但是 _ 变量仍然指代它,所以仍然可以获取原来引用的对象。
>>> a_set = {2, 3, 4}
>>> wref()
{0, 1}
>>> _
{0, 1}

# 4. 计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。
# 但是,随后 _ 绑定到结果值 False,现在 {0, 1} 没有强引用了。
>>> wref() is None
False
>>> _
False

# 5. 因为 {0, 1} 对象不存在了,所以弱引用指向的是 None,所以 wref() 返回的是 None。
>>> wref() is None
True

8.6.1 WeakValueDictionary简介:

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

示例:

import weakref


class Cheese:
    def __init__(self, kind):
        self.kind = kind

    def __repr__(self):
        return 'Cheese(%r)' % self.kind



# 1. 创建个弱引用类型的字典
stock = weakref.WeakValueDictionary()

# 2. 奶酪列表
catalog = [Cheese('Red Leicester'), Cheese('Tilsit'), Cheese('Brie'), Cheese('Parmesan')]


for cheese in catalog:
    # 3. 将 stock 以奶酪的名为键,然后把值映射到 catalog 中 Cheese 的实例的弱引用上。
    stock[cheese.kind] = cheese

# 以键名排序,现在 stock 是完整的
print(sorted(stock.keys()))  # ['Red Leicester', 'Tilsit', 'Brie', 'Parmesan']

# 4. 删除奶酪列表,即删除 stock 中值的弱引用对象,所以值对应的键也会随即删除。
del catalog
print(sorted(stock.keys()))  # ['Parmesan']

# 5. 显示删除全局变量 cheese 后,最后一个 Parmesan 消失。
del cheese
print(sorted(stock.keys()))  # []

注意示例中的第 4 点,删除 catalog 后,列表中的大部分奶酪示例都被删除了,但是为什么还有一个 Parmesan 呢?这是因为临时变量 cheese 引用了对象,如果是在局部变量里(函数的内部变量),这么就不会有这个问题,因为在函数返回时局部变量都会被销毁。 但是这里的 cheese 是全局变量,除非显式的删除,否则不会消失,所以 cheese 最后一个引用的对象 Parmesan 引用计数没有归零,也不会被垃圾回收,所以 WeakValueDictionary 中的弱引用是仍然可以获取到的对象。再次显式的删除 cheese,最后一个 Parmesan 便会消失。

WeakValueDictionary 对应的是 WeakKeyDictionary,后者的键是弱引用。WeakKeyDictionary 实例可以为应用中其他部分拥有的对象 附加数据,这样就无需为对象添加属性。这对覆盖属性访问权限的对象尤其有用。

weakref 模块还提供了 WeakSet 类,按照文档的说明,这个类的作用很简单:“保存元素弱引用的集合类,元素没有强引用时,集合会把它删除”。 如果一个类需要知道所有实例,一种好的方案是创建一个 WeakSet 类型的类属性,保存实例的引用。如果使用常规的 set,实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与 Python 进程一样长,除非显式删除类。

 8.6.2 弱引用的局限:

不是每个 Python 对象都可以作为弱引用的目标(或称所指对象)。基本的 listdict 实例不能作为所指对象,但是它们的子类可以轻松地解决这个问题:

class MyList(list):
    """list的子类,实例可以作为弱引用的目标"""


# a_list 可以作为弱引用的目标 
a_list = MyList(range(10))

wref_to_a_list = weakref.ref(a_list)

set 实例可以作为所指对象,但是 inttuple 实例不能作为弱引用的目标,甚至它们的子类也不行。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python的垃圾回收机制是自动管理内存的种机制,它通过检测不再的对象并释放其占用的内存空间,以提高程序的性能和效率Python使用引用计数来跟踪和收对象。每个对象都有一个引用计数,当对象引用时,引用计数加1;当对象不再被引用时,用计数减1。当引用计为0时,说明该对象不再被使用,可以被垃圾回收。 除了引用计数,Python还使用了循环垃圾收集器来处理循环引用的情况。循环引用指的是一组对象之间相互引用形成的环状结构,导致它们的引用计数都不为0,无法被释放。循环垃圾收集器会定期扫描内存中的对象,找出循环引用并将其回收。 下面是一个示例,演示了Python垃圾回收机制的用例: ```python class MyClass: def __init__(self): print('Creating an instance of MyClass') def __del__(self): print('Deleting an instance of MyClass') # 创建对象 obj1 = MyClass() obj2 = MyClass() # 创建循环引用 obj1.other = obj2 obj2.other = obj1 # 删除对象引用 del obj1 del obj2 # 手动触发垃圾回收 import gc gc.collect() ``` 在上面的示例中,我们创建了两个`MyClass`的实例`obj1`和`obj2`,并将它们相互引用形成循环引用。当我们删除对这些对象引用时,它们的引用计数变为0,但由于存在循环引用,它们不会被立即释放。通过手动调用`gc.collect()`函数,我们可以触发垃圾回收器来回收这些无法访问的对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值