最近用python在做项目,借此深刻认识到python果然是越学越难。
弱类型虽然方便,但也会导致各种各样意想不到的bug。
前言
在python中,万物皆是对象,变量名是访问到变量的唯一方式(通过引用访问)。
变量存储
内存中有两块区域:堆区与栈区。在定义变量时,变量名与值内存地址的关联关系存放与栈区,变量值存放于堆区。
内存回收
Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题,并且通过“分代回收”(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率。
引用计数
变量值被关联次数的增加或减少,都会引发引用计数机制的执行(增加或减少值的引用计数),这存在明显的效率问题。
如果说执行效率还仅仅是引用计数机制的一个软肋的话,那么很不幸,引用计数机制还存在着一个致命的弱点,即循环引用(也称交叉引用)。
1 | 123] l1=[ |
循环引用导致:即使值不被变量名引用,引用计数也不会为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。