每天五道面试题(4)
xrange和range的区别?
只有在python2中才有xrange和range,python3中没有xrange,并且python3中的range和python2中的range有 本质的区别。
在Python2中:
xrange:生成一个生成器对象
range:生成一个列表
在Python3中:
Python3其实是取消了Python2中的range,同时把Python2中的xrange改名为了range。
如果大家在Python2中生成一个range,然后type一下,他是列表类型的。
Python3呢,他是range类型的。
Python垃圾回收机制?
Python会进行自动的垃圾回收。
Python的垃圾回收机制有三种,第一种是引用-计数,第二种是标记-擦除,还有一种是分代回收。
引用-计数
原理
每个对象都维护一个引用计数字段,记录这个对象被引用的次数。
如果有新的引用指向对象,对象引用计数就加一,应用被销毁时,对象引用计数减一,当用户的引用计数为0时,该内存被释放。
优点
简单
实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
缺点
需要去维护引用计数,存在执行效率问题
无法解决循环引用问题
例子:循环引用
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
print(list1)
print(list2)
------------
有一组对象的引用计数不为0,但是这组对象实际上并没有被变量引用.
它们之间是相互引用,而且也不会有其他的变量再去引用这组对象.
最终导致如果使用引用计数法这些对象占用的内存永远不会被释放,从而导致内存泄露
标记-擦除
循环引用在Python中形成来一个“孤岛”或是一组未使用的、互相指向的对象,但是谁都没有外部引用,这时候我们想要回收这部分的内存,但是由于所有的引用计数都是1而不是0,无法进行垃圾回收。
所以Python要将循环引用摘掉,就会得出这两个对象的有效计数,同时还要引入Generational GC(Generational garbage collection)算法:
Python使用一种名为零代(Generation Zero)链表来持续追踪活跃的对象。每次我们创建一个对象或其他值的时候,Python会将其加入零代链表。
- 当我们创建
ABC
节点的时候,Python将其加入零代链表,注意这并不是一个真正的链表,并不能直接在你的代码中访问。
当我们创建DEF
节点的时候,Python将其加入同样的链表。
-
Python会循环遍历零代链表上的每个对象,检查链表中每个互相引用的对象,根据规则减掉其引用计数,这一步是
检测循环引用
。
ABC
和DEF
节点包含的引用数为1。同时有三个其他的对象同时存在于零代链表中,蓝色的箭头指示了有一些对象正在被零代链表之外的其他对象所引用。
-
通过识别内部引用,Python能够减少许多零代链表对象的引用计数。在下图的第一行,能够看见
ABC
和DEF
的引用计数已经变为零了,这意味着收集器可以释放它们并回收内存空间了
分代回收
-
理想状态下,Python创建了多少个对象,一段时间后就应该回收多少的垃圾。
-
但是实际上,由于上述的循环引用状态的存在,以及有一些对象被长时间的引用,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。
-
Python中存在一个Garbage collection阈值,当被分配对象的计数值与被释放对象的计数值之间的差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上述的标记-清除机制,释放“浮动的垃圾”。
-
Python采用分代回收的机制,实际上是基于弱代假说(weak generational hypothesis)提出的:这个假说由两个观点构成:年轻的对象通常“死”得也快,老对象则很有可能存活更长的时间。
-
基于此,分代回收的核心行为是:垃圾回收器会更频繁的处理新对象。一个新的对象即是你的程序刚刚创建的,而一个老的对象则是经过了几个时间周期之后仍然存在的对象。Python会在当一个对象从零代移动到一代,或是从一代移动到二代的过程中提升(promote)这个对象。
-
分代回收的意义在于:通过频繁的处理零代链表中的新对象,Python的垃圾收集器将把时间花在更有意义的地方,它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足阈值的条件,收集器才回去处理那些老变量
总结
第一阶段:引用-计数,最简单,最好用的垃圾回收机制,但是无法处理循环引用的情况。
第二阶段:标记-擦除,引入零代链表,将内存放入链表通过算法对引用进行计数。
第三阶段:分代回收,将存在时间长的链表升级为一代,二代链表,让他们存活更长的时间。注意力放在零代链表上,对零代进行回收。
使用fromkeys时的连坐机制
弄懂他的连坐机制,先看看这段程序。
v = dict.fromkeys(['k1','k2'],[])
v['k2'].append(666)
print(v)
v['k1'] = 777
print(v)
结果:
{'k1': [666], 'k2': [666]}
{'k1': 777, 'k2': [666]}
我们明明是对k2的value进行了追加元素的操作,为什么k1的value值也被修改了呀。
这就是因为,k2的value与k1的value实际上是对一个列表的不同引用。在内存之中,他们是一个列表,有一个发生修改,另一个也会发生修改。
一行代码实现9*9乘法表
print('\n'.join(['\t'.join(["%2s*%2s=%2s"%(j,i,i*j) for j in range(1,i+1)]) for i in range(1,10)]))
def func(a,b=[]) 这种写法有什么坑?
这个坑可以参考连坐机制。
def func(a,b=[]):
b.append(a)
print(b)
func(1)
func(1)
func(1)
func(1)
推导一下运行结果 ,你就明白了。