前言
本文除\"总结\"外,其余均为认识过程;3.7.5;
总结:
如果在同一代码块下,则采用同一代码块下的缓存机制;
如果是不同代码块,则采用小数据池的驻留机制;
需要注意的是,交互式输入时,每个命令都是一个代码块;
实现 Intern 保留机制的方式非常简单,就是通过维护一个字符串储蓄池,这个池子是一个字典结构,编译时,如果字符串已经存在于池子中就不再去创建新的字符串,直接返回之前创建好的字符串对象;
如果之前还没有加入到该池子中,则先构造一个字符串对象,并把这个对象加入到池子中去,方便下一次获取;
长度为0与1的字符串一定会被驻留;
字符串驻留发生在程序编译时;
不同代码块中,被驻留的字符串必须由 ASCll 字母, 数字以及下划线组成;
字符串的长度限制问题目前没有发现,就是没有发现超过 20 之后就不行了;
1.代码块的缓存机制
Python 程序是由代码块构造的。块是一个 Python 程序的文本,它是作为一个单元执行的。
代码块:一个模块, 一个函数, 一个类, 一个文件等都是一个代码块;交互方式输入的每个命令都是一个代码块;
交互方式:就是在 cmd 中进入 Python 解释器里面,每一条指令都是一个代码块;
Python 在执行同一个代码块的初始化对象的命令时,会检查其值是否存在,如果存在,会将其重用;
满足代码块的缓存机制则它们在内存中只存在一个,即:id相同;
代码块的缓存机制的适用范围: int(float),str,bool;
int(float): 任何数字在同一代码块下都会复用;
bool: True 和 False 在字典中会以 1,0 方式存在,并且复用;
str:同一代码块中,值相同的字符串在内存中只存在一个:
a1 = 1000
b1 = 1000
a1 is b1 # True
f1 = 100.0
f2 = 100.0
print(f1 is f2) # True
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,无论你创建多少个变量指向 True,False,它在内存中都只存在一个;
小数据池也是只针对 int,str,bool;
小数据池是针对不同代码块之间的缓存机制;
# cmd, -5~256 的小整数虽然不在同一代码块中, 但是它们适用小数据池机制
>>>a = 245
>>>b = 245
>>>a is b # True
>>>f1 = 100.0
>>>f2 = 100.0
>>>print(f1 is f2) # False
# 长度为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
3.试一试
a1 = 1000
b1 = 1000
print(a1 is b1) # True
f1 = 100.0
f2 = 100.0
print(f1 is f2) # True
class C1(object):
f = 10.0
a = 100
b = 100
c = 1000
d = 1000
s = \'skj\'
class C2(object):
f = 10.0
a = 100
b = 1000
s = \'skj\'
print(C1.s is C2.s) # True
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
print(C1.f is C2.f) # False
4.优缺点
优点:值相同的字符串的(比如标识符),直接从池里拿来用,避免频繁的创建和销毁,提升效率,节约内存;
缺点:拼接字符串、对字符串修改之类的影响性能;
因为是不可变的,所以对字符串修改不是 inplace 就地操作,要新建对象,这也是为什么拼接多字符串的时候不建议用 + 而用 join();
join() 是先计算出所有字符串的长度,然后一一拷贝,只 new 一次对象;
小整数对象池
为避免整数频繁申请和销毁内存空间,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 中,每次运行时,所有代码都加载到内存中,属于一个整体,所以这个时候会有一个大整数对象池处于一个代码块的大整数是同一个对象; 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 机制的使用策略是有考究的,有些场景会自动使用 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
参考:
https://www.zhihu.com/question/29945705 python里的怪问题
https://www.pianshen.com/article/9128116263/ python-小数据池,代码块深入剖析
https://www.dazhuanlan.com/2020/01/16/5e1f70e908538/ cpython 中的 string interning
来源:https://www.cnblogs.com/htzy/p/14623406.html
图文来源于网络,如有侵权请联系删除。