最近用python在做项目,借此深刻认识到python果然是越学越难。

弱类型虽然方便,但也会导致各种各样意想不到的bug。


前言

在python中,万物皆是对象,变量名是访问到变量的唯一方式(通过引用访问)。

变量存储

内存中有两块区域:堆区与栈区。在定义变量时,变量名与值内存地址的关联关系存放与栈区,变量值存放于堆区。

内存回收

Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题,并且通过“分代回收”(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率。

引用计数

变量值被关联次数的增加或减少,都会引发引用计数机制的执行(增加或减少值的引用计数),这存在明显的效率问题。

如果说执行效率还仅仅是引用计数机制的一个软肋的话,那么很不幸,引用计数机制还存在着一个致命的弱点,即循环引用(也称交叉引用)。

1
2
3
4
5
6
7
8
>>> l1=[123]
>>> l2=["456"]
>>> l1.append(l2)
>>> l2.append(l1)
>>> print(l1)
[123, ['456', [...]]]
>>> print(l2)
['456', [123, [...]]]

循环引用导致:即使值不被变量名引用,引用计数也不会为0。由于相互引用的存在,这些对象占用的内存永远不会被释放。所以Python引入了“标记-清除” 与“分代回收”来分别解决引用计数的循环引用与效率低的问题。

标记-清除

标记的过程其实就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。
清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

分代回收

分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)。

新定义的变量,放到新生代这个等级中,假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一,当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代,青春代的GC扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间,接下来,青春代中的对象,也会以同样的方式被移动到老年代中。也就是等级(代)越高,被垃圾回收机制扫描的频率越低。

虽然分代回收可以起到提升效率的效果,但也存在一定的缺点:例如一个变量刚刚从新生代移入青春代,该变量的绑定关系就解除了,该变量应该被回收,但青春代的扫描频率低于新生代,所以该变量的回收就会被延迟。

可变对象和不可变对象

可变对象包括list、set、dict等,不可变对象包括int、float、long、str、tuple等。

对于可变对象,对象的操作不会重建对象,而对不可变对象,每一次操作就会重建新的对象。

在函数参数传递时,其实就是把参数里传入的变量的对象的引用赋值给函数内部变量。

深拷贝与浅拷贝

copy.copy 浅拷贝,只拷贝父对象,不会拷贝对象的内部的子对象。

copy.deepcopy 深拷贝,拷贝对象及其子对象。

除此以外,使用列表生成式,for循环遍历,使用切片都是浅拷贝方法。

is与==

==只比较两个对象的内容是否相等,不比较地址。

is比较的是两个对象是否完全相同,即内容相同并且地址相同。

最后一条,None的bool值为False。