python 中 is 和 == 的区别、(不)可变量、tuple和float与其他不可变量的不同

1) is和==的区别:

简单来说,is和==的区别在于:

is:通过id来判断两个对象是否相等,
==:通过value判断两个对象的值是否相等。

python中可以将数据类型简单的分为两类:可变量和不可变量。

不可变量:如字符串、范围在[-5,256]的数值。(is相等只对这两类有效,float和tuple不能这么用,后面解释)
可变量:如列表,字典等。

之前看到这篇文章说,可变和不可变量的区别在于,在id和type相同的情况下,看value是否会变。

2)不可变量:str和int([-5,256])

字符串和范围内的数值的id在value不做更改的情况下,是不可能变的。

因为事实上,当它被改变的时候它就被指向了另一个地址。并且,一切需要被传给另一个变量的对象,它都已经是另一个对象。

比如str的replace,split,upper,他们是复制了一份原来的对象,然后对他们做处理,从而生成另一个对象,他们不是在原对象本身做改变。

>>> a = 'dfdf'
>>> a.upper()
'DFDF'
>>> b = a.upper()
>>> id(a),id(b),id(a.upper())
(50214392L, 46570032L, 46568992L)
>>> id(a),id(a.upper()),id(b)
(50214392L, 46568992L, 46570032L)
>>> 

可以看到,每次对同一个字符upper的id都不一样。

python的字符串有缓存,如果它们的引用为0的时候,即原内容不被引用时,如上的upper,会自动销毁。

3)可变量:list、dict等

而列表与字符串不同,它的列表是一个整体,它的元素如果是通过append等添加更改,id不会变;但是如果你重新赋了一个类似的列表值,id就会发生改变。

>>> a = [123]
>>> id(a)
49980232L
>>> a.append(4)
>>> id(a)
49980232L
>>> a = [1,2,3]
>>> id(a)
49989512L

4)is和==的一般和非一般情况:

对于==没有什么意外情况,因为只要值相等,可变量与不可变量没有什么区别,他们都会相等。

而对于is,一般情况下,不可变量,如两个字符串和数字的id和值都是相等的,用is和None没有区别(例1和例3)。而对于可变量,如列表,字典等,即便他们的元素个数和值都一样,他们的id也不会相等(具体例子可以参照例4)。

非一般情况下,以下有几个例子做对比:

1)

>>> a = 1
>>> b = 1

2)

>>> a = 257
>>> b = 257

3)

>>> a = '1'
>>> b = '1'

4)

>>> a = [1]
>>> b = [1]

5)

>>> a = 1;b = 1

6)

>>> a = 257
>>> b = 257
>>>def test():
       c = 257
       print a is c
   print a is b

分别对他们进行is和==比较:

1)

>>> a = 1
>>> b = 1
>>> a is b
True
>>> a == b
True

2)

>>> a = 257
>>> b = 257
>>> a is b
False
>>> a == b
True

3)

>>> a = '1'
>>> b = '1'
>>> a is b
True
>>> a == b
True

4)

>>> a = [1]
>>> b = [1]
>>> a is b
False
>>> a == b
True

5)

>>> a = 257;b = 257  # 需要在IDLE上进行验证,在ipython上测试不成功

https://zhuanlan.zhihu.com/p/31169016这篇教程说,同一个代码块中的两个值相同的整数会被重用,意思是is的结果是True。

起初我在ipython上测试并没有成功,他们的id仍不相同。与缓存无关,我在ipython中测试的,开了两个窗口,一个该对象已经存在,一个不存在,结果都是一样的。

后来在python自带的IDLE中测试成功了,原因是在 Python 的交互式命令行 REPL 中,每单独一行都视为一个代码块,同一行中的代码属于同一个代码块,后者重用了前者。

6)

>>> a = 257
>>> b = 257
>>>def test():
       c = 257
       print a is c  # False
>>> a is b # True
>>> test() # False

这个的顺序其实是先比较的a和b,再比较a和c。
在py文件中a和b是相等的,但在shell和ipython上都是False。
如上一个链接所说,在一个文件中和使用分号一个意思,在一个代码块中,属于模块级别。而c是局部变量,因此函数里的257这个对象会被重新创建,而不会和a,b指向同一个位置。

例1和例3就是对不可变量的id值不变的一个证明。例4是对可变量的一个证明。

至于例2,还记得前面提到过的,范围在[-5,256]的数值。

这里是因为在python中,为了提高性能,所以将一些常用的整数缓存起来,直接从缓存里拿,而不用每次都去创建。因为对象在不被引用之后就会被销毁,如果对于一些非常常用的东西如数字1,每次我们随手测试都要去创建一个数值为1的对象,然后再销毁,就显得很没有必要了。而这个整数的范围就是[-5,256]。

5) 浮点数float

说到整数,就会想起浮点数,它也是不可变量啊,但是因为浮点数的小数可以有很多,缓存就实在没有必要了,并且你能选中和它一模一样的概率不大。

并且它有一个很严重的问题,比如下一段代码,你猜它的结果是什么?

>>> i = 0.5
>>> while i != 1.0:
    print i
    i += 0.1

你可能和我一样,觉得它会执行到1.0然后结束循环,但事实上它会进入一个死循环。为什么呢?我们把这个while循环手动的去写一个计算试试:

>>> 0.5+0.1+0.1+0.1+0.1+0.1

你以为它的结果是1.0,错了,结果是0.9999……

>>> 0.5+0.1+0.1+0.1+0.1+0.1
0.9999999999999999

这就是为什么会死循环的原因,他们根本不会相等啊。

这篇文章给出了原因,大多数的float型数值并不能以精确的形式在计算机中表示出来,只能以十分接近原数值的形式储存,因此在计算过程中会出现小的误差。这并不是Python的BUG,而是计算机在表示实际时普遍存在的问题。

6) 元组tuple

而说到不可变量,又会想起tuple,它在创建之后也是不可变的,那么它算不算不可变量呢?
它的数据类型确实属于不可变对象,可它的本质却和数组差不多,和c里的数组一样,每次创建都会分配内存空间。
我们想想,tuple真的永远不变吗?如果它其中的一个元素是一个列表呢?它确实不能再被编辑,但是你不能阻止它包含的对象是个可变对象并发生改变。


>>> a = (1,)
>>> b = (1,)
>>> id(a),id(b)
(40394648L, 47206528L)

记住了,当你的tuple元素只有一个时,要在末尾加上一个逗号以示区别。如果没有逗号,你会发现,它其实是个整数类型:

>>> c = (1)
>>> type(c)
int

至于原因,链接2里提到了一个intern机制,于是就去查了一下什么叫intern机制,可以参考链接3

文章里说道,intern机制是针对字符串的,完整应该叫string intern,即字符串intern机制。

值同样的字符串对象仅仅会保存一份,是共用的,这也决定了字符串必须是不可变对象。
其实我们想一想,就和数值类型一样,同样的数值仅仅要保存一份即可了,不是必须用不同对象来区分。
intern机制的优点是,在创建新的字符串对象时,会先在缓存池里面找是否有已经存在的值相同的对象(标识符,即只包含数字、字母、下划线的字符),如果有,则直接拿过来用(引用),避免频繁的创建和销毁内存,提升效率。

以上几段可以解释字符串和整数的不可变。而tuple没有intern机制,它就是一个数组,因此它每次都是生成新的对象。

参考:
1. https://zhuanlan.zhihu.com/p/31169016
2. https://blog.csdn.net/vincent_ceso/article/details/76473834
3. https://blog.csdn.net/june_young_fan/article/details/79750589

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章