详解Python小数据池和代码块缓存机制
本文深入解析Python中的小数据池与代码块缓存机制。详细阐述int、str、bool在[-5, 256]范围内的缓存规则,以及字符串驻留(Intern)策略。对比交互式环境与IDE中整数和字符串缓存行为的差异,分析不可变对象对内存效率的影响,并提供字符串拼接的性能优化建议。
前言
本文除“总结”部分外,主要记录了对Python缓存机制的认知探索过程。关于Python 3.7.5版本中部分官方文档未明确说明的内容,目前尚未找到确切出处,如有读者知晓相关文档位置,欢迎留言补充,感谢分享!
总结:
核心规则如下: 若变量位于同一代码块内,则遵循代码块缓存机制; 若变量位于不同代码块,则遵循小数据池的驻留机制; 需特别注意,在交互式命令行中输入的每一条命令均被视为独立的代码块。
Intern(字符串驻留)机制的实现原理十分直观:系统维护一个字符串储蓄池,其底层为字典结构。在编译阶段,若字符串已存在于池中,则直接返回该已有对象,不再创建新实例;若字符串尚未加入池中,则先构造字符串对象并插入池中以供后续复用。
满足以下条件的字符串会被强制驻留: 长度为0或1的字符串; 驻留行为发生在程序编译阶段; 字符串内容仅由ASCII字母、数字及下划线组成。
1.代码块的缓存机制
Python程序由代码块构成,代码块是作为执行单元处理的Python文本片段。 常见的代码块包括:模块、函数、类定义、文件等; 在交互模式下(如cmd进入Python解释器),用户输入的每条命令均构成一个独立的代码块。
Python在执行同一代码块内初始化对象的指令时,会检查该值是否已存在。若存在,则直接重用该对象。 满足代码块缓存机制的对象在内存中仅保留一份,表现为id相同。 该机制适用的数据类型包括:int(含float)、str、bool。
具体类型表现如下: int(float):同一代码块内的任意数字均会被复用; bool:True和False在字典中分别以1和0的形式存在,且会被复用; str:同一代码块中,值相同的字符串在内存中仅存在一个实例。
s1 = 'janes@!#*ewq' s2 = 'janes@!#*ewq' print(s1 is s2) # True a1 = 'janes45613256132!@#$%#^%@$%' * 1 b1 = 'janes45613256132!@#$%#^%@$%' * 1 print(a1 is b1) # True s1 = 'hah_' * 6 s2 = 'hah_' * 6 print(s1 is s2) # True
2.小数据池
Python自动对-5至256范围内的整数进行缓存。将这些整数赋值给变量时,不会创建新对象,而是直接引用已存在的缓存对象。 对于满足特定规则的字符串,Python会在字符串驻留池中创建一份副本。赋值给变量时,同样直接引用池中的对象,而非重新创建。 bool值(True/False)无论有多少变量指向它们,在内存中始终只存在一个实例。
小数据池同样仅针对int(含float)、str、bool类型。 小数据池主要用于处理不同代码块之间的对象缓存问题。
# cmd, -5~256 的小整数虽然不在同一代码块中, 但是它们适用小数据池机制 >>>a = 245 >>>b = 245 >>>a is b # True
# 长度为0与1的字符串一定会被驻留; # 字符串驻留发生在程序编译时; # 被驻留的字符串必须由 ASCll字母, 数字以及下划线组成; >>>s1 = '@' >>>s2 = '@' >>>s1 is s2 # True >>>s1 = '' >>>s2 = '' >>>s1 is s2 # True >>>s1 = 'a_b_c' >>>s2 = 'a_b_c' >>>s1 is s2 # True >>>s1 = 'a b_c' >>>s2 = 'a b_c' >>>s1 is s2 # False >>>s1 = 'a_b_c' * 1 >>>s2 = 'a_b_c' * 1 >>>s1 is s2 # True >>>s1 = 'abd_d23' * 3 >>>s2 = 'abd_d23' * 3 >>>s1 is s2 # True >>>a, b = "some_thing!", "some_thing!" >>>a is b # False >>>a, b = "some_thing", "some_thing" >>>a is b # True
a1 = 1000 b1 = 1000 a1 is b1 # True class C1(object): a = 100 b = 100 c = 1000 d = 1000 class C2(object): a = 100 b = 1000 print(C1.a is C1.b) # True print(C1.a is C2.a) # True print(C1.c is C1.d) # True print(C1.c is C2.b) # False
3.优缺点
优点:对于值相同的字符串(如标识符),可直接从池中获取,避免了频繁的创建和销毁操作,从而提升运行效率并节约内存空间。
缺点:字符串拼接或修改操作会影响性能。 由于字符串是不可变对象,修改操作并非就地(inplace)进行,而是需要创建新对象。这也是为什么在拼接多个字符串时,推荐使用join()而非+运算符的原因。 join()方法会预先计算所有字符串的总长度,然后一次性拷贝内容并仅创建一次对象。
小整数对象池
为避免整数频繁申请和销毁内存,Python引入了小整数对象池。Python将[-5, 256]范围内的整数定义为小整数,这些对象在启动时已预先创建,且不会被垃圾回收。 在任意Python程序中,无论小整数出现在LEGB作用域的哪个层级,只要数值在此范围内,所有引用均指向同一个对象。
# 3.7.5, ipython7.18.1 a = -5 b = -5 a is b # True a = -6 b = -6 a is b # False a = 256 b = 256 a is b # True a = 257 b = 257 a is b # Flase
大整数对象池
在cmd终端中,每次赋值大整数时,通常会重新创建对象; 而在PyCharm等IDE中,所有代码在运行时加载到内存中,被视为一个整体,因此存在一个大整数对象池,使得同一代码块内的大整数成为同一对象。 变量c和d处于同一代码块,因此相等;而C1.b和C2.b分别属于各自的类定义代码块,因此不相等。
# cmd 终端 a = 1000 b = 1000 a is b # False -------------------- class C1(object): a = 100 b = 100 c = 1000 d = 1000 class C2(object): a = 100 b = 1000 print(C1.a is C1.b) # True print(C1.a is C2.a) # True print(C1.c is C1.d) # True ?? 难道 cmd 中也有大整数池 ?? 类加载的时候是在一块内存中,同值同地址 ?? print(C1.c is C2.b) # False # pycharm 等编辑器中 a = 1000 b = 1000 a is b # True -------------------- class C1(object): a = 100 b = 100 c = 1000 d = 1000 class C2(object): a = 100 b = 1000 print(C1.a is C1.b) # True print(C1.a is C2.a) # True print(C1.c is C1.d) # True print(C1.c is C2.b) # False
字符串驻留机制
Python解释器为提高字符串使用效率,在编译时采用了Intern(字符串驻留)技术。该机制确保值相同的字符串对象仅保存一份,并置于共享的字符串储蓄池中。由于字符串必须是不可变对象(整数同理),因此浮点数无法采用此机制。
简单原理:
Intern机制通过维护一个字典结构的字符串储蓄池来实现。编译时,若字符串已存在,直接返回;若不存在,则构造并加入池中以供后续获取。 然而,解释器内部对Intern策略的使用有特定考量。某些场景下会自动启用Intern,而另一些场景则需要手动触发。具体见下文常见情景分析。
# cmd 中浮点数没有被缓存 a = 1.0 b = 1.0 a is b # False # cmd 中并非全部的字符串都会采用intern机制; 仅 包括下划线、数字、字母的字符串才会被 intern--类标识符 s1="hello" s2="hello" s1 is s2 # True # 如果有空格,默认不启用intern机制 s1="hell o" s2="hell o" s1 is s2 # False s1 = "hell!*o" s2 = "hell!*o" print(s1 is s2) # False # 如果一个字符串长度超过20个字符,不启动intern机制 -- 看网上很多都是这么写的, 不超过二十个就为真,但是我在自己 3.7/8.5 版本上试了一下,发现好像没有限制,不知道是 Python 更新了,还是什么问题…… s1 = "a" * 20 s2 = "a" * 20 s1 is s2 # True s1 = "a" * 21 s2 = "a" * 21 s1 is s2 # True s1 = "ab" * 10 s2 = "ab" * 10 s1 is s2 # True s1 = "ab" * 11 s2 = "ab" * 11 s1 is s2 # True # 'kz' + 'c' 编译时已经变成 'kzc',而 s1 + 'c' 中 s1 是变量, 会在运行时进行拼接,所以没有被intern? 'kz' + 'c' is 'kzc' # True s1 = 'kz' s2 = 'kzc' s1+'c' is 'kzc' # False # pycharm 等编辑器中,只要是同一个字符串,都为 True,并不用是下划线、数字、字母的字符串 s1 = "hell o" s2 = "hell o" print(s1 is s2) # True s1 = "hell!*o" s2 = "hell!*o" print(s1 is s2) # True s1 = "a" * 20 s2 = "a" * 20 print(s1 is s2) # True s1 = "a" * 21 s2 = "a" * 21 print(s1 is s2) # True s1 = "ab" * 10 s2 = "ab" * 10 print(s1 is s2) # True s1 = "ab" * 11 s2 = "ab" * 11 print(s1 is s2) # True 'kz' + 'c' is 'kzc' # True s1 = 'kz' s2 = 'kzc' s1+'c' is 'kzc' # False # 编辑器中,float 也被缓存了 a = 1.0 b = 1.0 a is b
以上就是详解Python小数据池和代码块缓存机制的详细内容,更多关于Python小数据池和代码块缓存机制的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













