博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python中的字符串驻留(String Interning)
阅读量:6318 次
发布时间:2019-06-22

本文共 3549 字,大约阅读时间需要 11 分钟。

在Python中操作字符串时,有时可能会遇到一些奇怪的现象,例如下面这个例子:

>>> a = "hello">>> b = "hello">>> a is bTrue>>> a = "hello world">>> b = "hello world">>> a is bFalse复制代码

你可能会问:为什么会这样呢?答案是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 == bTrue复制代码

由此可以知道,如果a is bTrue,也就是说 a 和 b 指向同一个内存地址,那么a == b也必定为True。这点没有任何疑惑。

但是回到文章开头处,有:

>>> a = "hello world">>> b = "hello world">>> a is bFalse复制代码

上面这个结果向我们透露了两个信息:

  1. a 和 b 指向不同的内存地址
  2. 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 bTrue>>> a = "kjhuwhoehiwh98yu398y1____ajs9f9">>> b = "kjhuwhoehiwh98yu398y1____ajs9f9">>> a is bTrue>>> a = "python is great!">>> b = "python is great!">>> a is bFalse复制代码

编译时常量和运行时表达式

下面介绍一个更加让人迷惑的现象:

>>> 'a' + 'b' is 'ab'True>>> a = 'a'>>> a + 'b' is 'ab'False复制代码

我们用dis包将上面的代码编译成Python字节码:

import disdef 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))复制代码

运行结果:

TrueFalse************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_VALUENone************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_VALUENone复制代码

大家可以看到,如果常量的值能够在编译时就确定,那么就会被驻留;如果必须在运行时才能确定,那么就不会驻留。你还可以尝试下面的例子:

>>> a = 'a'>>> a * 20 is 'aaaaaaaaaaaaaaaaaaaa' # a * 20 在编译时无法确定其值False>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' # 'a' * 20 在编译时可以确定其值True复制代码

另外还可以尝试:

>>> x, y = 'hello', 'hello'>>> x is yTrue>>> x, y = 'hello!', 'hello!'>>> x is yFalse复制代码

总结

两点:

  • 当且仅当只包含下划线、数字、字母的字符串才会被驻留;
  • 编译时可以被确定的常量值会被驻留,运行时才能确定的值不会指向编译时驻留的字符串。

以上结果,均在Python 3.7.2中验证。更低版本的Python可能会出现不同的结果。如果出现了自己不能理解的结果,建议使用dis包,将代码编译为字节码,即可明白其原理。


Python交流学习群:

微信公众号:小鑫的代码日常

转载于:https://juejin.im/post/5c923b8a5188252d8d190777

你可能感兴趣的文章
oracle10g总结
查看>>
mysql优化思维引导一
查看>>
夏日里的激情——FE鹅和鸭农庄行
查看>>
架构设计目录
查看>>
mysql-mmm故障解决一例
查看>>
多角度认识markdown
查看>>
集中化监控SQL Server数据库
查看>>
cacti监控批量加,省时省力又省心。
查看>>
客户端性能测试通过performanceCounter监控客户端性能指标
查看>>
ORACLE的直方图的一些试验
查看>>
Linux下备份系统
查看>>
Android应用程序键盘(Keyboard)消息处理机制分析(20)
查看>>
基于WinSvr2012共享文件夹的Hyper-V实时迁移之三实时迁移的实现及验证
查看>>
Axure 全局辅助线(转)
查看>>
RHEL6 PXE+KickStart全自动安装配置指南
查看>>
SQL2K数据库开发六之表操作创建产品表products
查看>>
巧用Linux 架设TFTP Server备份路由器的配置文件
查看>>
关于MYSQL 字符转义问题总结
查看>>
视频数据:深度数据采集(Depth Data)
查看>>
红帽集群套件RHCS四部曲(概念篇)
查看>>