Python隐藏特性:字符串驻留、常量折叠

287 阅读3分钟

下面是Python字符串的一些微妙的特性,绝对会让你大吃一惊。

案例一:

a = "some_string" id(a) 140420665652016

id("some" + "_" + "string") # 注意两个的id值是相同的. 140420665652016

案例二:

a = "wtf" b = "wtf" a is b True

a = "wtf!" b = "wtf!" a is b False

a, b = "wtf!", "wtf!" a is b True # 3.7 版本返回结果为 False.

案例三:

'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' True

'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' False # 3.7 版本返回结果为 True

很好理解, 对吧?

💡 说明: 这些行为是由于 Cpython 在编译优化时, 某些情况下会尝试使用已经存在的不可变对象而不是每次都创建一个新对象. (这种行为被称作字符串的驻留[string interning])

发生驻留之后, 许多变量可能指向内存中的相同字符串对象. (从而节省内存)

在上面的代码中, 字符串是隐式驻留的. 何时发生隐式驻留则取决于具体的实现. 这里有一些方法可以用来猜测字符串是否会被驻留:

所有长度为 0 和长度为 1 的字符串都被驻留.

字符串在编译时被实现 ('wtf' 将被驻留, 但是 ''.join(['w', 't', 'f']) 将不会被驻留)

字符串中只包含字母,数字或下划线时将会驻留. 所以 'wtf!' 由于包含 ! 而未被驻留. 可以在【地址1】找到 CPython 对此规则的实现.

当在同一行将 a 和 b 的值设置为 "wtf!" 的时候, Python 解释器会创建一个新对象, 然后同时引用第二个变量(译: 仅适用于3.7以下, 详细情况请看【地址2】). 如果你在不同的行上进行赋值操作, 它就不会“知道”已经有一个 wtf!对象 (因为 "wtf!" 不是按照上面提到的方式被隐式驻留的). 它是一种编译器优化, 特别适用于交互式环境.

常量折叠(constant folding) 是 Python 中的一种 窥孔优化(peephole optimization) 技术. 这意味着在编译时表达式 'a'*20会被替换为 'aaaaaaaaaaaaaaaaaaaa' 以减少运行时的时钟周期. 只有长度小于 20 的字符串才会发生常量折叠. (为啥? 想象一下由于表达式 'a'*10**10 而生成的 .pyc 文件的大小). 相关的源码实现在【地址3】.

如果你是使用 3.7 版本中运行上述示例代码, 会发现部分代码的运行结果与注释说明相同. 这是因为在 3.7 版本中, 常量折叠已经从窥孔优化器迁移至新的 AST 优化器, 后者可以以更高的一致性来执行优化. (由 Eugene Toder 和 INADA Naoki 在 bpo-29469 和 bpo-11549 中贡献.)

最后,小编想说:我是一名python开发工程师,整理了一套最新的python系统学习教程, 想要这些资料的可以关注私信小编“01”即可,希望能对你有所帮助。