【语法篇】垃圾回收和弱引用

141 阅读6分钟

文章一览

del的作用

在某些代码中,我们可以看到del这个语句,例如

class people:
    def __init__(self, name = "张三"):
        self.name = name
p = people()
P1 = p
P2 = P1
del p
del P1

有些文档中介绍到,del可以清除掉某个对象占用的内存空间,通过del可以更好保留更大的内存空间。但是del真的可以直接清除掉内存的使用吗?
答案是不能,在回答这个问题之前,我们需要先了解一下引用,什么是引用呢,p = people(),其实就是一组引用,变量p引用了对象people
p = people()其实执行了两步操作,第一步是先在内存空间中开辟出一处空间来保存对象people,然后将变量p和对象people绑定在一起。

垃圾回收和弱引用1.png 如图所示,=的作用就是在变量和对象之间添加一层引用,那么,del的作用就是与之相反的,是删除掉变量与对象之间的引用
真正实现内存清理的其实是Python内置的垃圾回收策略,当没有变量指向这个对象的时候,也就是没有变量与对象之间的连线的时候,这个对象就会被回收掉。从而腾出内存空间来给其他对象使用。

垃圾回收流程

为了方便我们更好的观察到Python是如何进行垃圾回收的,我们需用用到一个内置的第三方库weakref,也就是弱引用,这个后面会提到,我们主要了解一下weakref.finalize,它可以绑定一个对象与函数,当对象被清理的时候,会回调这个函数。

import weakref
class people:
    def __init__(self, name = "张三"):
        self.name = name
def bye():
    print("对象被删除")
def run():
    p = people()
    weakref.finalize(p, bye)
print("run函数开始运行")
run()
print("run函数运行完毕")
run函数开始运行
对象被删除
run函数运行完毕

通过这段代码,我们可以清楚的看到,在局部作用域内的对象(例如函数内部创建的对象),将在函数执行完毕后被清理掉。接下来我们看看全局作用域创建的对象。

import weakref
class people:
    def __init__(self, name = "张三"):
        self.name = name
def bye():
    print("对象被删除")
p = people()
weakref.finalize(p, bye)
P1 = p
P2 = P1
print("准备清理变量p的引用关系")
del p
print("变量p的引用关系被清理")
print("准备清理变量P1的引用关系")
del P1
print("变量P1的引用关系被清理")
print("程序运行完毕....")
准备清理变量p的引用关系
变量p的引用关系被清理
准备清理变量P1的引用关系
变量P1的引用关系被清理
程序运行完毕....
对象被删除

从这里可以看出,在全局作用域里面的对象,将在程序运行完毕关闭时被自动清理掉,也进一步验证了del并不能直接从内存中删除对象

import weakref
class people:
    def __init__(self, name = "张三"):
        self.name = name
def bye():
    print("对象被删除")
p = people()
weakref.finalize(p, bye)
P1 = p
P2 = P1
print("准备清理变量p的引用关系")
del p
print("变量p的引用关系被清理")
print("准备清理变量P1的引用关系")
del P1
print("变量P1的引用关系被清理")
print("准备清理变量P2的引用关系")
P2 = "helo"
print("变量P2的引用关系被清理")
print("程序运行完毕....")
准备清理变量p的引用关系
变量p的引用关系被清理
准备清理变量P1的引用关系
变量P1的引用关系被清理
准备清理变量P2的引用关系
对象被删除
变量P2的引用关系被清理
程序运行完毕....

可以看到,即便没有del,如果我们改变了变量与对象的引用关系之后,Python也会自动帮我们清理掉不用的对象。

总结

  1. 垃圾回收程序是Python自动进行的,一般不用我们主动进行处理
  2. del并不是直接删除掉对象,而是解除变量与对象之间的引用关系,重新绑定也可以起到这个作用
  3. 当某个对象没有被其他变量引用是,将会被Python自动回收掉

弱引用

弱引用是指为对象添加一个引用,但是这个引用并不会影响到对象的回收。

Users = {}
class User:
    def __init__(self, uid):
        self.uid = uid
U = User(1)
Users[1] = U

来看这样一段代码,分析对象的引用,首先实例对象U引用了这个User(1)对象,Users中的1也引用了这个对象。

垃圾回收与弱引用2.png 可以看到,此时有两根实线的引用关系,我们称为强引用,当一个对象存在强引用(即便只剩一条实线),对象也无法被垃圾回收。

import weakref
Users = {}
class User:
    def __init__(self, uid):
        self.uid = uid
def bye():
    print("对象被销毁")

U = User(1)
Users[1] = U
weakref.finalize(U, bye)
del U
print("程序运行结束...")
程序运行结束...
对象被销毁

可以看到,即便我们删除掉了U的引用,但是对象仍然一直存在,直到程序运行完毕后才被销毁,这无疑会导致我们在程序运行过程中产生不必要的内存消耗,为了防止这样的事情,我们需要了解一下弱引用。

import weakref
Users = {}
class User:
    def __init__(self, uid):
        self.uid = uid
def bye():
    print("对象被销毁")

U = User(1)
Users[1] = weakref.ref(U)
weakref.finalize(U, bye)
del U
print("程序运行结束...")

垃圾回收与弱引用3.png 通过weakref.ref(U)实现字典对对象的弱引用,也就是如图中绿色虚线标注的那种,这种形式可以保障当实线关系被删除的时候,对象就会被删除,虚线只能使用,而不能阻止对象被回收

对象被销毁
程序运行结束...

弱引用api

ref

import weakref
class User:
    def __init__(self, uid):
        self.uid = uid
U = User(1)
t = weakref.ref(U)
print(t())
del U
print(t())

通过ref可以构建对象的弱引用,可以通过t()获取到这个对象,当对象被删除后,t()的值为None。

WeakValueDictionary

import weakref
d = weakref.WeakValueDictionary()
class User:
    def __init__(self, uid):
        self.uid = uid
U = User(1)
d['u'] = U
print(d['u'])
print(len(d))
del U
print(len(d))
print(d['u'])

WeakValueDictionary可以构造一个弱引用的字典,以value(值)为准,也就是把对象设置为value,key是任意的,且该字典与常用的字典一样,不同的是当对象被删除后,该字典里面会自动删除这条记录。特点是值为弱引用对象,key任意

WeakKeyDictionary

import weakref
d = weakref.WeakKeyDictionary()
class User:
    def __init__(self, uid):
        self.uid = uid
U = User(1)
d[U] = U
print(d[U])
print(len(d))
del U
print(len(d))
print(d[U])

WeakValueDictionary使用一致,不同点在于key为弱引用对象,值任意

finalize

import weakref
class User:
    def __init__(self, uid):
        self.uid = uid
U = User(1)
def bye():
    print("对象被删除...")
weakref.finalize(U, bye)
del U

绑定对象与某个回调函数,在对象被删除时,将运行回调函数