做为一门简单易用的语言,且配备海量的库,python可谓是程序员手中的掌中宝,编程自己就是一种将人类思惟转化为计算机思惟的技术,若是不须要去追求极致的运行效率同时又不限制于计算机内存空间,python无疑是目前最方便的语言了。html
做为一个合格的程序员,天然是要知其然并知其因此然,除了可以应用python来放飞自我以外,同时也要探究python其内部的运行原理,首当其冲的python编程中必需要用到的变量以及背后的运行机制。java
注:如下示例在linux平台下编写,使用python2.7python
python的变量-内存模型更像是C++中的引用机制,python中的每一个变量不必定占用内存空间,变量更像是一分内存的引用,经过这个变量能够访问到内存中的数据,举个例子:linux
>>>a=10 >>>b=a >>>c=[1,2,3,4] >>>d=c >>>print "%x%x" %(id(a),id(b)) >>>print "%x%x" %(id(c),id(d))
输出结果:程序员
b51080.b51080 7f28bf69b758.7f28bf69b758
其中id()是python的系统函数,返回对象的内存起始地址。编程
从结果能够看出,a与b,c与d变量对应的地址事实上为同一个地址,也就是当咱们使用变量a和b时,使用的是同一个对象,而a,b是这个对象的引用,咱们能够经过系统函数sys.getrefcount()来查看一个对象的引用数量:缓存
>>>import sys >>>a=257 >>>print sys.getrefcount(a) >>>b=a >>>print sys.getrefcount(a)
输出结果:框架
2 3
显然,这个结果并不在咱们的预料当中,因为a和b在同一个地址,结果应该是一、2,为何是2,3呢?python2.7
这是由于在sys.getrefcount()函数调用时,a做为参数也被引用了一次,因此出现了二、3的结果。函数
上面讲了python变量赋值时的内存机制,事情就这么完美结束了吗?
并无!!!
咱们再来看一个例子:
>>> a=10 >>> b=10 >>> print "%x.%x" %(id(a),id(b))
输出结果:
b51080.b51080
看到这个结果,我缓缓摘下个人眼镜,拿95%浓度的医用酒精仔仔细细擦了三遍以后再戴上看,没看错!这两个变量仍是同一个地址内容的引用,这一次两个变量的初始化是独立的,并不是赋值初始化,为何两个变量仍是同一个地址的引用呢?
答案是:
在Python中,Python会有一个缓存对象的机制,以便重复使用。当咱们建立多个等于1的引用时,其实是让全部这些引用指向同一个对象,以达到节省资源的目的
原来是这样!!!
可是仔细一想,这不对吧?若是每一个数据都进行缓存,那岂不是对内存空间的极度浪费?仍是说内存回收机制会过一段时间回收一次垃圾内存?
咱们再来看下面一个例子:
>>> a=100 >>> b=100 >>> print "%d%d" %(id(a),id(b)) >>> a=256 >>> b=256 >>> print "%d%d" %(id(a),id(b)) >>> a=257 >>> b=257 >>> print "%d%d" %(id(a),id(b))
输出结果:
5223836.5223836 5225932.5225932 5241840.5241864
从结果来看,当a小于256时,这个值会被系统缓存循环利用,而当a>256时,系统并不会进行缓存(固然不只仅是三次实验的结果,博主后续还试了不少值,就不一一列出了)
咱们来用另外一种方法来验证这个问题,即sys.getrefcount():
>>>import sys >>>a=10 >>>print sys.getrefcount(a) >>>a=257 >>>print sys.getrefcount(a)
输出结果为:
15 2
结果显而易见,10这个值被系统缓存,且在别处引用了屡次,而257这个值为2(为何为2而不是1在上面有解释)
那么问题又来了,若是是其余类型的数据呢?咱们接着看
>>>a="downey" >>>b="downey" >>>print "%d%d" %(id(a),id(b))
结果为:
39422528.39422528
短字符串也会有缓存机制
而后是list:
>>>a=[1,2,3] >>>b=[1,2,3] >>>print "%d%d" %(id(a),id(b)) 39704576.39745176
list并无缓存机制,从这里能够看出,python的缓存机制并不针对全部变量类型
根据各类实验以及多方查证,结果代表:
看到这里,喜欢思考的朋友们不由就要问了,缓存这些整型数据和短字符串真的对性能有明显提高吗?python代码中能有多少个整型变量?
答案是:整型变量对应整型的内存对象,可是整型的内存对象并不只仅对应整型的变量类型,容器中的整形元素可能也是整形变量的引用
若是你还有疑惑,咱们来看看下面的例子:
>>> import sys >>> a=1 >>> sys.getrefcount(a) 128 >>> b=[1,2,3] >>> sys.getrefcount(a) 129
从打印的结果能够看出,整型变量a=1,表示a指向对象1,为1的引用,b[0]也被初始化为1,一样的,b[0]同时也是对象1的引用,对于全部容器而言,都是这种形式,看到这里,各位观众老爷们应该是有所理解了吧。
关于python变量内存机制对变量使用的影响能够参考这一篇博客:python函数调用时参数传递方式
既然说到了内存机制,必然涉及到分配和回收的机制,内存分配就很简单,在定义对象的时候用到进行内存的分配,而内存的回收则没那么简单,由于在内存回收的过程当中,python没法执行其余任务,因此频繁地内存回收会致使严重的效率问题,而内存回收间隔时间过长则会致使内存浪费严重,因此通常只有在特定时间内启动内存回收。
python运行时,会记录下来分配和释放的次数,只有当两个值的差大于某个数值时,即
分配次数-释放次数>触发回收的阈值
时,python进行垃圾回收,咱们可使用get_threshold()方法来获取阈值:
>>>import gc >>>print gc.get_threshold()
输出结果:
(700,10,10)
这个700即是触发内存回收的阈值。可是后面的两个10又是什么意思呢?
这也是内存回收中的一种机制,叫作分代回收,这一策略的基本假设是:存在时间越久的对象,越不可能成为垃圾对象,即给予一些长期使用的对象更多信任。
Python将全部的对象分为0,1,2三代。全部的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被纳入下一代对象。垃圾回收启动时,必定会扫描全部的0代对象。若是0代通过必定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了必定次数的垃圾回收后,那么会启动对0,1,2,即对全部对象进行扫描
这两个次数即上面get_threshold()返回的(700, 10, 10)返回的两个10。也就是说,每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。
咱们也能够手动地调整触发回收的阈值,聪明的朋友们能够猜到这个方法了,既然有get,必然相对应的就是set:
import gc gc.set_threshold(600,8,7)
除了被动地等待系统回收,固然也能够手动地进行内存回收:
import gc gc.collect()
其实java也好,python也好,每一种语言的内存机制将从根本上影响语言的执行效率,因此在内存的处理上会有不少更加复杂的细节,这里只是介绍了一个大致的框架,班门弄斧,欢迎路过的大神们指正和补充。
好了,关于python变量内存机制的问题就到此为止了,若是朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言
我的邮箱:linux_downey@sina.com 原创博客,转载请注明出处!
祝各位早日实现项目丛中过,bug不沾身. (完)