Python对象的管理
在学习数据结构和基础算法之前,我们首先应该了解 Python 当中关于对象的一些小细节。
众所周知,Python 是一门拥有垃圾回收机制(GC)的语言。我们不需要手动的掌控对象什么时候会销毁,Python 在运行的时候会自动地使用内置的垃圾回收机制来决定是否从内存中销毁它们。同样的,我们也无法直接使用Python操作内存来回收某些对象。
浅谈内存中的对象管理
Python 的垃圾回收以引用计数为主,辅以其他回收机制。
为了解释引用计数,看下面一段代码
a = [1, 2]
b = a
b.append(3)
你可以尝试输出 a 与 b,可以发现两者都变成了[1, 2, 3]
。因为这里的 a 与 b,都只是一个指向我们最开始申明的对象的名称。就像你出生时,你的父母为你取了一个名字,当你开始接触网络,你为自己也取了一个新的名字。无论是哪个名字,都只是一个名字而已,它们都是指向你。
在 Python 里,当一个对象没有任何一个名称指向它时,它就会被 Python 当中的 GC 机制给回收掉。我们可以使用del
手动删除一个名称,但无法具体的掌握一个对象什么时候会被删除。
接下来思考一个问题——如果我们在两个不同的对象里分别创建一个属性指向对方,它们会怎么样?这种互相指向,或者说互相引用,就被称为循环引用。
循环引用
对于引用计数回收机制,最致命的缺点就是循环引用,它可能会引发致命的内存泄漏(两个对象互相指向对方,对于引用计数回收机制来说,这两个对象将永远无法被回收)。Python 虽然针对这种情况有对应的回收方法,但我们不能知道它什么时候会执行,它或许永远也不会执行(我在因为代码里的一段循环引用导致内存泄漏时没见过它执行,整整一个星期)。
所以当我们不得不让两个对象的属性互相指向对方的时候,就要请出weakref
这个标准库。下面看一个在Python交互式环境下执行的例子
>>> import weakref
>>> a = Node()
>>> a_ref = weakref.ref(a)
>>> a_ref
<weakref at 0x100581f70; to 'Node' at 0x1005c5410>
>>> print(a_ref())
<__main__.Node object at 0x1005c5410>
>>> del a
Data.__del__
>>> print(a_ref())
None
>>>
顾名思义,这个库的作用就是——弱引用。这种引用不增加引用计数机制当中的引用数,当被引用对象不存在时,将返回None
。在 JavaScript 这种同样使用引用计数回收机制的语言里,同样有类似方式创建弱引用,这几乎是一种通用的解决方式。
有兴趣了解更深的 Python 垃圾回收机制的可以自行搜索,这不是本书的重点,所以不再赘述。
Python 对象的复制
由于 Python 里的这种赋值形式导致了我们无法使用诸如a = b
这种表达式来拷贝一个新的对象——它们都指向内存里的同一个对象,因此Python也提供了复制对象的一个库——Copy。
这个库很简单,它对外开放的只有两个可用函数——copy.copy(obj)
,copy.deepcopy(obj)
。
copy.copy
它的拷贝是一种浅层次的拷贝。对于字符串,整数等非嵌套类型对象,它所表现出来的和deepcopy
完全相同。但对于列表,字典等嵌套对象类型,它仅仅只会拷贝它本身,而它里面的对象将不会被拷贝,仅仅会被引用。
copy.deepcopy
它的拷贝是一种递归的拷贝,它拷贝完对象本身之后,如果内部还有对象,那么它会接着拷贝内部的对象。如此递归下去,直到所有的对象都拷贝完。
Python 的常量池
当你看完上述的复制对象的方式后,也许会兴冲冲去尝试,但结果就像下面的代码一样。
>>> import copy
>>> a = "asdfjsd3242df12!"
>>> b = a
>>> c = copy.copy(a)
>>> a is b
True
>>> a is c
True
>>>
你也许会疑惑,说好的创建新对象呢?a、b、c 怎么还是指向内存里的同一个对象。这是因为 Python 对一些内建对象进行了缓存,常见的缓存有简单字符串、小整数等。如果要测试复制对象,建议使用list
、dict
等复杂对象。
小结
本节概要的讲述了有关于Python对象管理的部分内容。虽然不够全面,也不够深入,但对于接下去的数据结构和算法学习已经够用了。所以不在本书中做更多的介绍。
Last updated