1、Python引用,变量,对象
python中的对象可以分为不可变对象和可变对象两种类型,分别是:小整数、简单字符串(对于整数和字符串为什么要强调小整数和简单字符串参考博客
Python缓存机制),,而对于大整数,长字符串,列表、字典等。
区别可变对象和不可变对象还要用到内存和引用,当我们执行一句代码a=3时,python中执行了三个动作:
首先,为整数对象3分配一段内存空间,用来存放对象3,
再次,创建一个变量a
最后,变量a引用对象3
同样list_a=[1,2,3]也是做了同样的事情
a=3
b=a
a=4
我们打印出a和b,分别为4和3,第一行代码是将变量a引用了对象3,其实就是给对象三贴上了一个标签a,第二行代码,等同于将变量b也引用了对象3,这样对象3上贴了两个标签了,第三行代码将变量a又重新引用了对象4,也就是说,将对象3上的标签a撕下来贴到对象4上了。以上这个过程对象3没有任何改动,所以我们称之为不可变性
list_a=[1,2,3]
list_b=list_a
list_a[0]=4
我们打印出list_a和list_b,结果都是[4,2,3],因为我们的list_a和list_b都是引用的列表对象,而列表对象中又包含了三个整数对象,也即list_a[0],list_a[1],list_a[2]三个列表的index分别又引用了1,2,3三个对象,这样list_a[0]=4知识修改了列表对象第一个index引用的对象,我们list_a和list_b两个变量直接引用的列表对象并没有改变。
对于赋值,对于字符串对象有个需要注意的地方,因为字符串分为简单字符串和复杂字符串
简单字符串如:‘a’,‘hello’
复杂字符串如:‘hello world’,‘a,’
a=‘hello’
b=‘hello’
和
a=‘hello’
b=a
这两种赋值效果都是一样的,内存中只分配了一个hello对象的内存,a和b都引用同一个对象
a=‘hello world’
b=‘hello world’
这种赋值,a和b指向的是不同的内存地址,引用的不同的对象
a=‘hello world’
b=a
这种赋值,a和b是指向的同一内存地址,引用的同一对象
关于==和is
python中==是对比值是否一样不关心内存地址,而is既关心值是否 一样也关心内存地址是否一样,说白了就是是否是同一个对象
a=‘hello’
b=‘hello’
和
a=‘hello’
b=a
例如,对于简单字符串和整数对象,上面的赋值a和b是同一个对象,即a==b且a is b
a=‘hello world’
b=‘hello world’
而对于上面这样的赋值,a=b,但是a is b是False,正如字符串赋值中讲到的,a和b内存地址不一样了,不是同一个对象。
list_a=[1,2,3]
list_b=[1,2,3]
这样赋值的话,list_a和list_b就指向不同的对象了,通过id(list_a)和id(list_b)可以看出他们分别指向不同的内存地址
a=3
b=3
如此赋值,a和b指向的是同一个对象,通过id(a)和id(b)可以看到他们都指向的同一内存地址
这里提现了可变对象和不可变对象的区别,也可以称之为简单对象和复杂对象
2、Python中的copy和deepcopy
对于整数和字符串等不可变对象来说,b=a和b=copy.copy(a)的效果是相同的,大家都引用的同一个对象
对于可变对象来说list_b=copy.copy(list_a)后,list_a和list_b分别指向不同的内存地址的对象,copy是重新在内存中开辟了一个空间存放copy后的对象。
那copy和deepcopy的区别是什么呢?
他们的区别在于针对嵌套的数据结构来说的,比如[1,2,3,[4,5,6]]这种列表中嵌套列表,或者字典中嵌套字典,列表嵌套字典,字典嵌套列表等等,要知道这些数据结构中的值都是一个内存中对象的引用,所以对于copy来说,它只会复制某个数据结构的第一层引用对应的内存对象,而deepcopy会进行递归的copy,一层一层的复制出来,重新开辟一个内存空间存放所有对象,直到最后的简单对象。
3、Python垃圾回收机制
Python的垃圾回收机制是建立在引用的基础上的,对于一个对象,可以有多个变量引用它,每个对象中都保存一个属性,即被引用次数(引用计数器),当一个变量不在引用该对象时,对象的引用计数器就减1,如果该对象的引用计数器变为0,该对象所占用的内存空间就会被回收。
Python的垃圾回收机制是Python中引用为0了不使用了的对象Python就会自动对其进行回收,这样的话Python会反复的进行内存的分配和回收,而python在进行垃圾回收时不能执行其他的任务,这样会大大影响Python的执行效率,所以python不是时时刻刻都会对引用变为0的对象进行回收,而是只有垃圾数量达到一定阈值时,才会启动自动垃圾回收,python中有对象分配和取消对象分配两个计数器(其实就是内存分配次数和内存),只有当两个计数器的差值大于一定的阈值时,才会启动垃圾回收,这个阈值可以通过python的gc模块进行查看
import gc
print (gc.get_threshold())
返回(700,10,10)表示对象分配减去对象取消数大于700时才会启动垃圾回收
同样,这个阈值可以设置
gc.set_threshold(700,10,10)
后面的两个10是分代回收的概念后面会提到
我们也可以手动的启动垃圾回收和关闭垃圾回收
gc.collection()
gc.disabled
更多的gc模块的方法参见 https://docs.python.org/2/library/gc.html
分代回收
python的垃圾回收也采用了分代的策略,基本思想就是存活时间越久的对象越不可能是垃圾,因此会相应减少对这些存活时间比较久的对象的扫面,从而也提高python的效率。
python的分代回收分为三代,分别是0,1,2,刚分配的对象都是0代对象,如果某一个对象在一次垃圾扫描中幸存下来(也即是该对象的引用不为0),那么他就会被放到下一代中,如果在下一代扫描中又幸存下来,那么在放入下一代中。
为了减少扫描对象的次数,python通过gc.set_threshold(700,10,10)设置扫描的频率,其中的第一个10表示每扫描完10次0代对象,就进行一次0,1代对象的扫描,第二个10表示,每扫描完10次1代对象,就进行一次2代对象的扫描。
4、内存池
python的内存管理机制是分层实现的
-1和-2层是由操作系统来实现的
0层是由c来实现的,调用c的malloc和free来分配和释放内存
1层和2层是内存池,是python的底层接口
PyMem_Malloc来实现的,主要用来分配那些小于256k大小的内存,这样也避免了反复的调用底层的c函数来反复的分配和释放内存,提高了运行效率
3层是我们对python的直接操作