Python的字符串驻留机制,内存优化产生的小心思
介绍一个 python 函数 id(),它接收一个变量为输入,返回其在计算机内存当中的存储地址,例如:
a = "python"
print(id(a))
# Output: 4531959632
(输出结果仅做参考)
然后我们做这样一个实验:定义变量 a 和 b,其同时赋值字符串 "python",再查看它们的地址:
a = "python"
b = "python"
print(id(a), id(b))
# Output: 4519966544 4519966544
结果居然一模一样,这是为什么,明明 a 和 b 是两个变量,却使用了同一地方的内容?
这就引出了我们今天的重点,字符串驻留机制。
定义
Python 会维护一个字符串常量池,当代码中出现重复字符串时,python 不会在新的地方再保存一遍,而是直接找到常量池里对应的内容,让新的变量映射到那里,从而节约内存、提升运行速度。
为什么
因为字符串是一种不可变数据类型,一旦创建存储到某个变量后,就无法修改,改变该变量的内容也只是将该变量所指向的内存地址改变,而不是改变内存里的内容。
不可变数据类型:Python 中的 3 种标准类型 Number(数字),String(字符串),Tuple(元组) 是不可变数据类型。
因此,Python 利用了这一特点,让存储相同字符串的变量共用一块内存地址,就能减少程序的运行开销。
is 和 ==
在 Python 中做 if 判断,我们会遇到两种判断相同的方式 —— is 和 ==。例如:
那么为什么会这样呢?
答案:is 和 == 分别有自己的判断规则,而这规则与驻留机制有关系。
放到语言里,A is B 说的是“A 是 B”,即 A 与 B 是完全相同的,可能会更好理解。
a = "python"
b = "python"
print(a == b, a is b)
# Output: True True
# list 是可变数据类型
c = [1, 2, 3]
d = [1, 2, 3]
print(c == d, c is d)
# Output: True False
由于 list 是可变数据类型,因此尽管其保存的内容相同,但是却因为保存的地址不同而使得 is 判断时为 False。
驻留触发规则
首先要明确一点,并不是所有的字符串都会触发驻留机制!
必然触发的条件
必然触发的条件:
a = "python_123"
b = "python_123"
print(a is b)
# Output: True
不会触发的情况
与上面必然触发相反,出现空格、特殊符号等,就不会触发驻留机制:
a = "python 123"
b = "python 123"
print(a is b)
# Output: False
c = "python?"
d = "python?"
print(a is b)
# Output: False
一些 IDE(如 PyCharm)会对代码做额外的优化,使得带空格字符串也能触发驻留机制;但是 Python 原生提供的命令行交互工具是严格遵守触发规则的。
字符串拼接触发的驻留
- 当字符串的拼接是常量拼接时,代码会在编译过程即被优化,使得驻留机制被触发;
- 若通过字符串变量拼接的方式,则最终字符串只能在运行过程中产生(因为系统并不知道最终会得到什么结果,无法与现有字符串进行匹配),因此无法触发驻留机制。
a = "pyt" + "hon"
b = "python"
print(a is b)
# Output: True
x = "pyt"
c = x + "hon"
print(c is b)
# Output: False
这也说明了 Python 只对人为事先定义的内容做驻留机制判断。
手动驻留
对于一些特殊情况,例如我们读取的长日志、长文本,因为存在空格、特殊字符,如果让系统自己处理,则可能不触发自动驻留而导致内存浪费。
此时可以用 sys 模块的 sys.intern() 强制驻留:
import sys
s1 = sys.intern("python 123!")
s2 = sys.intern("python 123!")
print(s1 is s2)
# Output: True
整数驻留机制
除了字符串驻留机制,Python 也对一些小整数做了缓存,范围是 [-5, 256] 之间的整数。因为这些小整数是常见、常用的,因此设置驻留可以减少内存开销:
m = 10
n = 10
print(m is n)
# Output: True
x = 300
y = 300
print(x is y)
# Output: False
同样,这套机制会因 IDE 对代码做的额外优化而产生差异,使得当数字变为 300 时依然判断为 True;但是 Python 原生提供的命令行交互工具是严格遵守触发规则的。
往期回顾:
用OpenCV去除图像噪声增强细节 | Part.10
为什么程序员偏爱Dark Mode(深色模式)
dict的增删查改|Python自学成才(番外篇5)
用OpenCV来实现简单的人脸检测 | Part.9
键盘上的Enter键为什么叫“回车”?