Python札记1:字符串驻留(String Interning)
Posted xietx1995
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python札记1:字符串驻留(String Interning)相关的知识,希望对你有一定的参考价值。
在Python中操作字符串时,有时可能会遇到一些奇怪的现象,例如下面这个例子:
>>> a = "hello"
>>> b = "hello"
>>> a is b
True
>>> a = "hello world"
>>> b = "hello world"
>>> a is b
False
你可能会问:为什么会这样呢?答案是Python中有一种称为“字符串驻留(String Internning)”的机制。
is和==
在Python中,我们使用is
来判断两个对象的对象标识符(object identity)是否相等,也就是判断两个对象的内存地址是否相等,是不是同一个东西,即a is b
相当于检查id(a) == id(b)
:
>>> a = "hello"
>>> b = "hello"
>>> id(a) == id(b)
True
而==
只是用于判断两个对象的值是否相等(equality),也就是说,两个变量的值是否相等:
>>> a = "hello"
>>> b = "hello"
>>> a == b
True
由此可以知道,如果a is b
为True
,也就是说 a 和 b 指向同一个内存地址,那么a == b
也必定为True
。这点没有任何疑惑。
但是回到文章开头处,有:
>>> a = "hello world"
>>> b = "hello world"
>>> a is b
False
上面这个结果向我们透露了两个信息:
- a 和 b 指向不同的内存地址
- a 和 b 的值是相同的
我们可以用id函数来查看对象的标识符(地址):
>>> b = "hello world"
>>> a = "hello world"
>>> id(a)
1580069459248
>>> id(b)
1580069232944
可以看到,a 和 b 确实是不同的对象。产生这种情况的原因就是字符串驻留(String Interning)。
字符串驻留(String Interning)
Python中的字符串采用了驻留机制,当需要值相同的字符串的时候(比如标识符),可以直接从字符串池里拿来使用,也就是值相同的字符串在内存中只有一个对象。
这样做是为了避免频繁的创建和销毁,提升效率和节约内存。
因此拼接和修改字符串是会比较影响性能的。因为Python中的字符串是不可变的,所以字符串的操作都不是原址操作,而是新建对象,这也是为什么拼接多字符串的时候不建议用加号(+),而用join(),join()是先计算出所有字符串的长度,然后再拷贝,只new一次对象。
需要注意的是,并不是所有的字符串都会采用驻留机制,当且仅当只包含下划线、数字、字母的字符串才会被驻留。
因为 “hello world” 中包含了空格,所以不会驻留。如果满足驻留要求,那么就会驻留:
>>> a = "helloworld"
>>> b = "helloworld"
>>> a is b
True
>>> a = "kjhuwhoehiwh98yu398y1____ajs9f9"
>>> b = "kjhuwhoehiwh98yu398y1____ajs9f9"
>>> a is b
True
>>> a = "python is great!"
>>> b = "python is great!"
>>> a is b
False
编译时常量和运行时表达式
下面介绍一个更加让人迷惑的现象:
>>> 'a' + 'b' is 'ab'
True
>>> a = 'a'
>>> a + 'b' is 'ab'
False
我们用dis
包将上面的代码编译成Python字节码:
import dis
def bytecode1():
a = 'a' + 'b'
print(a is 'ab')
def bytecode2():
a = 'a'
b = a + 'b'
print(b is 'ab')
if __name__ == "__main__":
bytecode1()
bytecode2()
print("************compile-time*************")
print(dis.dis(bytecode1))
print("************run-time*************")
print(dis.dis(bytecode2))
运行结果:
True
False
************compile-time*************
4 0 LOAD_CONST 1 ('ab')
2 STORE_FAST 0 (a)
5 4 LOAD_GLOBAL 0 (print)
6 LOAD_FAST 0 (a)
8 LOAD_CONST 1 ('ab')
10 COMPARE_OP 8 (is)
12 CALL_FUNCTION 1
14 POP_TOP
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
None
************run-time*************
8 0 LOAD_CONST 1 ('a')
2 STORE_FAST 0 (a)
9 4 LOAD_FAST 0 (a)
6 LOAD_CONST 2 ('b')
8 BINARY_ADD
10 STORE_FAST 1 (b)
10 12 LOAD_GLOBAL 0 (print)
14 LOAD_FAST 1 (b)
16 LOAD_CONST 3 ('ab')
18 COMPARE_OP 8 (is)
20 CALL_FUNCTION 1
22 POP_TOP
24 LOAD_CONST 0 (None)
26 RETURN_VALUE
None
大家可以看到,如果常量的值能够在编译时就确定,那么就会被驻留;如果必须在运行时才能确定,那么就不会驻留。你还可以尝试下面的例子:
>>> a = 'a'
>>> a * 20 is 'aaaaaaaaaaaaaaaaaaaa' # a * 20 在编译时无法确定其值
False
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' # 'a' * 20 在编译时可以确定其值
True
另外还可以尝试:
>>> x, y = 'hello', 'hello'
>>> x is y
True
>>> x, y = 'hello!', 'hello!'
>>> x is y
False
总结
两点:
- 当且仅当只包含下划线、数字、字母的字符串才会被驻留;
- 编译时可以被确定的常量值会被驻留,运行时才能确定的值不会指向编译时驻留的字符串。
以上结果,均在Python 3.7.2中验证。更低版本的Python可能会出现不同的结果。如果出现了自己不能理解的结果,建议使用dis
包,将代码编译为字节码,即可明白其原理。
我的知乎:奔三的鑫鑫
欢迎关注微信公众号:小鑫的代码日常
欢迎加入Python学习交流群:532232743,这里有各路高手等着你~
以上是关于Python札记1:字符串驻留(String Interning)的主要内容,如果未能解决你的问题,请参考以下文章