python 不可变量和可变量(稍微深入)
程序员文章站
2024-01-23 14:16:34
...
@ 摘要:
Python的数据类型涉及到2个大原则 即 可变和不可变,是否可变显然说的变量代表的内存空间里的值。
本文简要概述python3的 (python2版本好像更奇葩) 并且和Java的相关内容进行类比。
以源码为依据,从时间和空间出发猜测为什么会这么做。
@ 作者: http://blog.csdn.net/vincent_ceso
@ 声明:不保证正确:) 转载无需注明 就说是你写的。
>>作者:http://blog.csdn.net/vincent_ceso
Python的不可变量
>字符
>数值
>元组
[不可变量的描述]
引用->对象:
- 对于不可变量如果需要创建的对象的内容(value值)相同,则引用都指向同一个对象,不创建新的内存空间。
- 理论上因为定义了值是不可变的。所以如果大家都一样的值,那就指向同一份内存空间好了。显然这么节省内存,避免冗余。
- 但是实现的时候显然考虑到记录和维护这些变量。那么每次赋值的时候还涉及到一个查找是不是已经存在,引起了时间和空间的矛盾。实际上python实现的时候可是因地制宜的,会有很多的细节琐碎处。
#[demo1 数值(int float)]
a = 10 #数值 但是是int 10
b = 10 #数值 但是是int 10
c = 10. #数值 但是是float 10.
print(a is b) #True 说明内存是相同的空间 这就是不可变导致的a和b数值相同,那么a,b指向同一个内存空间。
print(a == b) #True 数值相等
print(a is c) #False 显然数值不同,那么指向内存空间不同
print(a == c)
#True 因为==是运算,即int==float 的不同类型元素涉及类型转换。
#所以Python先把int变成float即再比较。显然是相同的
a = 11 #修改值 值不同 那么就是新的内存空间了
print(a is b) #False 显然数值不同,那么指向的内存空间不同
'''琐碎处:
python3 似乎没发现如下问题。
python2 :
如果你的测试数值大于[-5,256]就发现出错。是版本问题。不同版本处理int的实现方式不同。
[小整数对象使用对象池技术]:Python直接将这些小范围的整数对应的PyIntObject缓存在内存中,其指针放在small_ints中。预先存储好了一堆小数值。
因为python是有一个smallint数组来维护的。相当于解释器的一个缓存,对于smallint,即[-5,256]范围,所以如果你的值在这个范围里,那就直接指向预先设好的这个内存空间。如果超出那么就是开辟新空间存储。
为什么?
因为维护这么一个smallint数组必须是高效的。首先因为维护和预先设定内存存储数据那就是内存的开销,而到时候new一个int对象还要涉及到查找是不是已存在于smallint里的CPU开销。
如果你经常使用1 2 3 这三个 那么显然就针对这三个维护起来很高效啊。但是实际中数值是很难预测的,即很难命中已缓存的。所以维护太多是毫无意义的。而且多了到时候你就要用个数字a++一下。结果人家先找到这个数字。找了10ms这么慢再返回。那岂不是还不如直接开辟的好。
时间和空间的协调。所以维护了一个小范围常用的的池子即可。
'''
#[demo2 字符串]
a = "cesto"
b = "cesto"
c = "XJ"
print(a is b) #True 说明指向的内存是相同的空间 这就是不可变
print(a == b) #True 数值相等
print(a is c) #False 显然数值不同,那么指向的内存空间不同
print(a == c) #False 值不同
a = "XJ"
print(a is b) #False 值不同 指向的内存空间不同
print(a is c) #True 说明指向的内存是相同的空间
#说明没有新生成a 而是指向c指向的那个内存空间哦!!!
'''【琐碎处】:
如果测试数值相等,但是输出啊is b是False,还是版本问题。
因为字符串一般都是通过字典维护的,new一个记录下,下次如果是相同的那就直接指向相同内存空间不产生新的。
python3:实现了intern共享。
为啥?
因为字符串参与运算很少吧,存储的确很多。即字符串是一个存储为主导的。必然涉及到大量内存。所以节省内存是字符串高效的地方啊,所以我们舍弃时间追求空间。
python2里:没看过源码。似乎是有人说字符串很长很长的时候就无效。那么大概可以理解因为字符串那么太长涉及到的就是存储的压缩。导致维护的开销恒大,所以太长也就放弃了记录了。
'''
#[demo3 元组]
a = (1,"cesto",[1]) #一个新的元组(1,"cesto")被a所指向
b = (1,"cesto",[1]) #一个新的元组(1,"cesto")被b所指向
print(a is b) #Flase 为什么?因为其实此时此刻产生的是俩个对象。没有实现intern机制
print(a == b) #True 数值相等
''' 即此时此刻是两个元素a和b,虽然他们的值相同,但是是俩个对象。
而且这俩a和b是不可变的:
即你修改他们只能是重新赋值,其实是指向了新的内存空间,而不能对原来指向的内存空间做出修改。
原来的内存空间是还在的,然后很快就被GC掉。
本质就是没实现Intern
'''
'''【琐碎处】:
元组本身有很多不一样。就是因为元组的元素可以是任意的元素。即元素本身可以是不可变或者可变的。所以比较特殊。元组的不变就是说,凡是修改一个元组对象,必然是返回一个新的数组对象。而不可能是在原地址进行修改。
理论上应该和数值 字符串一样维护啊,但是没有?
为啥?
因为元组本身就没实现intern机制。其实本质元组是等价于C里的数组的。每次你new数组就是新创建。根本就没有维护和记录啊。
为啥不维护?
因为查找太慢啊。维护是为了避免冗余,但是相同的元组似乎出现的场景太少了吧那。
空间意义和时间意义都不大。那就不实现。
'''
#【看到一个以其昏昏,使人昭昭,强加附会的例子】
a = (1) #这是不是一个元组?
b = (1)
print(a is b) # True 为什么?
print(type(a)) # 因为(1)此时可不是元组 而是int
'''认为上述例子可以反驳 元组的实现情况 '''
a = (1,) #这才是一个元组
b = (1,)
print(a is b) # False 为什么?
print(type(a)) # 因为(1,)此时是元组
[小结]
- 1.即如果不可变量的值相同,那么数值相同理论上完全指向同一个内存空间
- 2.所以如果有指针修改了大家都指向的这一块内存空间 那么引用他的全部变量的值就一次性都变了。
好在不支持指针,Python没有指针是避免了这个问题。- 3.这样的不可变变量就是: 数值 字符串 元组
- 4.数值 字符串 基本都是按照“值相等那就是指向同一个内存空间” 避免冗余。内存浪费。
因为实现了Intern机制。但是要注意是一定范围内才有效。- 5.其中元组比较特殊。只是保证了不可原地修改。但相等的元组不是指向同一个内存地址。
因为本质tuple没有实现intern机制
Python的可变量
>列表
>字典
>集合
[可变量的描述]
引用->对象:
- 对于可变量只要创建对象那就是本质是new一个新的内存空间,但是好处是能够修改。即修改对象值不会新开辟对象而是就是在原来内存空间改。
a=[1,2,3]
b=[1,2,3]
print(a is b ) #false 每次new新的,才不维护呢
print(a==b) #true
'''可变量和引用是相对应的,即使对象的内容相等,也是不同的对象,修改的时候,两个对象互不影响'''
print(id(a)) #2810505330120
a.append(4) # 修改值
print(id(a)) #2810505330120 内存空间前后没变
下一篇: 内核中的延时函数