你所不知道的python--1
Interesting in python(1)
True or False in python console interactive and script file
python交互式解释和脚本执行中的对与错
整型
整型问题
正如你所看到的那样,在python解释器交互环境下逐行运行代码和将python代码写入文件以脚本的方式运行,有些时候可能会产生不同的结果。
@python 3.6.7
>>>a = 256
>>>b = 256
>>>a is b
True
>>>id(a) # id()函数,用于获取对象的内存地址
10918560
>>>id(b)
10918560
>>>
>>>a = 257
>>>b = 257
>>>a is b
False
>>>id(a)
140026888266672
>>>id(b)
140026888267824
>>>
但是如果是以脚本的方式或者是在交互环境中以代码块的方式执行,毫无疑问上述结果都是True。
@python 3.6.7
>>>def test():
... a = 257
... b = 257
... print(a is b)
... print(id(a))
... print(id(b))
...
>>>test()
True
140026888267728
140026888267728
>>>
>>>a, b = 257, 257 # 这样也可以实现a is b: True!
>>>a is b
True
>>>
以上代码是在python交互环境中以代码块的方式执行,你也可以将其中的函数保存到test.py文件中,在终端使用python test.py命令一样可以得到相同的结果。
一些解释
"python中一切皆对象"
在官方文档是这样解释的,当我们创建的整型在-5~256之间时,我们获取的是已经存在对象的引用,python对在此区间的整型对象用一个integer object array进行了缓存。注意只是对int型,float并不适用。
@python 3.6.7
>>>a = 256.0
>>>b = 256.0
>>>a is b
False
>>>
所以说我们在命令行之所以可以在创建int数值等于256时,可以获取相同的id是因为python本身就缓存了这些对象,我们尝试重新启动python解释器来查看int为256的id值。
@python 3.6.7
>>>a = 256
>>>id(a) # 可以发现和之前的id值是一样的
10918560
>>>def test():
... a = 256
... print(id(a))
...
>>>test() #id值仍然是一样的
10918560
>>>
@ubuntu:$ cat test.py
# -*- coding: utf-8 -*-
a = 256
print(id(a))
@ubuntu:$ python test.py
94547949255776
从中我们可以发现同样的int型数值在交互环境和脚本文件两种执行方式下id值是不同的,在交互环境下创建int数值在-5~256之间是引用的已经存在的整型对象,所以id值保持不变,当然这个值因机器和python版本而异(在python2中id值会变,但True or False的结果和python3是一样的);而以脚本方式运行则是新建的整型对象的引用,每次运行脚本都会得到不同的id值。
简单总结
在下结论之前我们先来理解一下编译中的内存优化管理,很多面向对象语言,包括C++、Java、python都有类似的机制,"为了节省内存的需要,会尝试使用已经存在的对象而不会每次都重新创建一个对象",当然不同语言之间实现各有不同,我们只需要知道是python解释器也会这样做。现在我们回到之前的问题上来,简单理解一下python解释器对命令行交互方式和存储好的脚本文件执行结果不同的原因:
- 命令行交互方式:对于交互环境中的每一行命令,python解释器都将其作为单独的代码块来执行。
- 脚本文件:对于脚本文件,python解释器将其本身视为整个代码块执行。
因为无论是在交互环境还是以脚本文件的方式,python解释器都遵循"为了节省内存的需要,python解释器在执行同一个代码块的初始化对象命令时都会尝试使用已经存在的对象而不会每次都重新创建一个对象",因此在命令行交互环境中,每一行命令都是不同的代码块,都会重新创建对象,但是由于-5~256之间的对象python已经缓存了,所以才会得到相同的id值。同样当我们在交互环境中将初始化代码放在同一行中或者定义一个函数(即同一个代码块中),不论数值多少,执行结果和脚本文件是一样的。在脚本文件中,所有代码视为一个代码块,因此在其中的赋值初始化都会尝试使用已经存在的对象,这也就是为什么在交互环境中的a,b赋值257,a is b: False!,而当定义在同一个代码块中时a,b赋值257,a is b: True!的原因。
我们再来看一下另外一种比较方式
@python 3.6.7
>>>a = 257
>>>b = 257
>>>a == b
True
>>>id(a)
140026888266672
>>>id(b)
140026888267824
>>>
这和之前的结论并不矛盾,python中 is 是比较引用的两个对象是否为同一对象,即id值--在内存的位置是否相同;而 == 是比较的数值,即257是否等于数值257,所以在需要比较的时候我们要思考选择何种方式。
字符串
字符串问题
和整型一样,在字符串中也有类似的有趣问题。
@python 3.6.7
>>>s1 = 'interesting_in_python'
>>>s2 = 'interesting_in_python'
>>>s1 is s2
True
>>>
看到这你可能会问上述代码并不处于同一个代码块,难道python也为这些字符串对象提前缓存了?当然缓存是缓存了,但并不像整型一样,是当你创建s1的时候,此时这个字符串对象被缓存,之后你再次创建相同字符串对象时会引用之前已经创建好的相同对象。这种行为被称为"字符串驻留",同样这种机制也为节省内存而生。
一些字符串驻留规则
@python 3.6.7
>>>s1 = 'interesting_in_python_1'
>>>s2 = 'interesting_in_python_1'
>>>s1 is s2
True
>>>s3 = 'interesting' + '_' + 'in' + '_' + 'python'
>>>s1 is s3 # 仍然是同一个对象
True
>>>s1 = 'interesting_in_python!'
>>>s2 = 'interesting_in_python!'
>>>s1 is s2 # 因为字符串中只包含字母、数字、下划线时才会被驻留
False
>>>s1 == s2 # 对象不同,但字符串的值是相同的
True
>>>
>>>s1, s2 = 'interesting_in_python!', 'interesting_in_python!'
>>>s1 is s2 #当然这样是可以的,处于同一代码块中
True
>>>
>>>s1 = '!'
>>>s2 = '!'
>>>s1 is s2 # 因为所有长度为0或者1的字符串都会被驻留,且这条规则优先级高
True
>>>
>>>s1 = '!!'
>>>s2 = '!!'
>>>s1 is s2 # 这样就不行了
False
>>>
>>>list = ['interesting', 'in', 'python']
>>>s1 = '_'join(list)
>>>s2 = '_'join(list)
>>>s1 is s2 # 使用join()生成的字符串将不会被驻留
False
>>>s1 == s2 # 对象不同,但字符串的值是相同的
True
>>>
>>>s1, s2 = '_'.join(list), '_'.join(list)
>>>s1 is s2 # 这样也不行
False
>>>
使用脚本运行
@python 3.6.7
@ubuntu:$ cat test.py
# -*- coding: utf-8 -*-
s1 = 'interesting_in_python!'
s2 = 'interesting_in_python!'
print(s1 is s2)
@ubuntu:$ python test.py # 和交互环境执行结果不同,因为处于同一代码块
True
@python 3.6.7
@ubuntu:$ cat test.py
# -*- coding: utf-8 -*-
list = ['interesting', 'in', 'python']
s1 = '_'.join(list)
s2 = '_'.join(list)
print(s1 is s2)
@ubuntu:$ python test.py # 使用join()函数,每次将会返回一个新的字符串对象
False
一些思考
与整型在交互环境中运行和以脚本文件执行一样,字符串在这两种方式中True or False的结果也不一样。如果以脚本的方式运行,即在同一个代码块中,python会对其中初始化代码尝试使用已经存在的不可变对象(python字符串为不可变对象)而不是每次都创建一个新的对象。在交互环境中,若不在同一个代码块中,字符串驻留需要满足一些规则。