欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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 内存空间前后没变