Python小整数对象池和字符串intern实例解析


Posted in Python onMarch 21, 2020

is用于判断两个对象是否为同一个对象,具体来说是两个对象在内存中的位置是否相同。

python为了提高效率,节省内存,在实现上大量使用了缓冲池技术和字符串intern技术。

整数和字符串是不可变对象,也就意味着可以用来共享,如100个“python”字串变量可以共享一个“python”字符串对象,而不是创建100个“python”字符串。

小整数对象池

为了应对小整数的频繁使用,python使用对小整数进行了缓存,默认范围为[-5,256],在这个范围内的所有整数被python完全地缓存,当有变量使用这些小整数时,增加对应小整数对象的引用即可。

>>> i = -5
>>> j = -5
>>> i is j # i和j是同一个对象
True
>>> i = 256
>>> j = 256
>>> i is j # i和j是同一个对象
True
>>> i = 257
>>> j = 257
>>> i is j # i和j是不同对象
False

由上面的实例可以看到,当变量在[-5,256]之间时,两个值相同的变量事实上会引用到同一个小整数对象上,也就是小整数对象池中的对象,而不会去创建两个对象。而当变量超出了这个范围,两个值相同的变量也会各自创建整数对象,所以两者对应的对象不同。

字符串intern

如果当前变量引用的字符串对象已经存在的话,直接增加对应字符串对象的引用,而不去创建新的字符串对象,这就是字符串intern机制。

 >>> i = "12"
 >>> j = "12"
 >>> i is j
 True

在详细探讨字符串intern机制之前,先看一个奇怪的问题:

 >>> i = "1 2"
 >>> j = "1 2"
 >>> i is j
 False

 i = "1 2"
 j = "1 2"
 print(i is j)

输出结果

 True

上述代码分开运行,结果为False,但是合在一起结果却为True,也就是说分开运行的时候,i,j指向不同对象,而合在一起的时候i,j却指向了相同对象。为了明白其中的缘由,需要简单理解python的编译机制。

编译机制

在python中,万物皆对象,包括代码本身也是一种对象。python用code对象表示代码,代码编译后产生code对象。通常一个作用域对应一个code对象。

i = "1 2"
j = "1 2"
print(i is j)

def f():
  pass

编译结果

2 0 LOAD_CONST 0 ('1 2')
2 STORE_NAME 0 (i)

3 4 LOAD_CONST 0 ('1 2')
6 STORE_NAME 1 (j)

5 8 LOAD_CONST 1 (<code object f at 0x00000200F257CF60, file "small_int.py", line 5>)
10 LOAD_CONST 2 ('f')
12 MAKE_FUNCTION 0
14 STORE_NAME 2 (f)
16 LOAD_CONST 3 (None)
18 RETURN_VALUE

Disassembly of <code object f at 0x00000200F257CF60, file "small_int.py", line 5>:
6 0 LOAD_CONST 0 (None)
2 RETURN_VALUE

上述代码中编译生成了两个code对象,一个代表全局作用域,另一个代表函数f。

code对象保存了变量,常量(常量字面量)以及编译结果。code对象用常量表来保存常量,考虑到一个常量可能出现多次,在一张表上保存一个常量多次太过于奢侈。所以code对象对每个常量只保存一次,在需要引用它的地方使用它在常量表的位置作为常量的表示。在上述编译结果中可以看到,"1 2"这个字符串常量使用了两次,编译的代码为"LOAD_CONST 0",这里的0就是"1 2"在常量表当中的位置。

由于编译的这个特性,在同一个code对象中的变量,如果它们引用了同一个常量,那么无论这个常量有没有缓冲机制,它们引用的都是同一个对象。

a = "12"
b = "12"
c = "1 2"
d = "1 2"
e = 257
f = 257
g = 2424234234234234
h = 2424234234234234
print(a is b, c is d, e is f, g is h)

输出结果

 True True True True

这个例子说明,在同一个code对象当中,常量(字面量)仅一份,这与缓冲机制无关,是编译特性。所以对于上述那个奇怪的问题就可以解释了,当i,j在同一个code对象(同一个作用域)中引用常量"1 2",它们引用的都是同一个对象。而当在python命令行中分开执行时,对于每一条语句,都是一个单独的code对象,这时起作用的是字符串intern机制,上述运行结果说明,字符串intern机制对"12"进行了intern,而对"1 2"没有进行intern。

编译机制与小整数对象池对比

i = 257
j = 257
a = i - 1
b = i - 1
c = i + 1
d = i + 1
print(i is j, a is b, c is d)

输出结果

 True True False

i和j引用同一个常量,这是编译机制,所以i与j指向同一个整数对象,后面a和b虽然相等,但不引用常量,此时启用小整数对象池,a,b都等于256,在对象池中,所以a,b引用同一个对象,后面c,d不在对象池中,所以两者对象不同。

这里有一点需要注意,没有变量参与的运算会被编译器直接优化成对应的常量,进而保存进常量表中。

字符串intern机制与字符缓冲池
在编译过程中,字符串intern机制将所有的变量名进行intern,但对常量进行的intern有一点特殊的限制。能够intern的常量必须只包含[a-zA-Z0-9_],即字母数字加下划线,如果含有其他字符,就不会intern。在运行过程中,通过计算得到的字符串不会intern。

字符串有一个和小整数对象池相似的字符缓冲池,用于在运行过程中缓存单个字符,所以计算得到的字符串虽然不会intern,但如果是单个字符,就会使用到字符缓冲池。

k = "bbb"
a = k[0]
b = k[0]
c = k[1:]
d = k[1:]
print(a, d)
print(a is b, c is d)

输出结果

 b bb
 True False

可以看到,a和b确实指向同一个对象,而c和d指向不同对象,这就是字符缓冲池。

编译机制与字符串intern对比

i = "1 2"
j = "12"
k = "__fjdslfjaskfas"

ii = "1 2"
jj = "12"
kk = "__fjdslfjaskfas"

def f():
  a = "1 2"
  b = "12"
  c = "__fjdslfjaskfas"
  return a is i, b is j, c is k

print("Code:", i is ii, j is jj, k is kk)
print(f"intern: {f()}")

输出结果

 Code: True True True
 intern: (False, True, True)

i包含空格,包含空格的常量不会被intern,而其他两个常量不包含其他字符,所以会被intern。

总结

1. python代码被编译成code对象,通常一个code对象对应于一个作用域,作用域中重复出现的变量名以及常量在code中只保存一次。

2. 字符串intern机制主要作用于编译过程,在编译收集完变量和常量时,对变量和常量进行intern,而后构建一个code对象。

3. 字符串intern对常量的intern有限制,能够intern的常量必须只包含[a-zA-Z0-9_],即字母数字加下划线,如果含有其他字符,就不会intern。

4. 小整数对象池和字符缓冲池都是作用于运行过程中,python缓存小的整数和字符,当有变量使用这些对象时,不用额外创建对象。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
使用Python下载Bing图片(代码)
Nov 07 Python
使用cx_freeze把python打包exe示例
Jan 24 Python
在Python的Flask框架中实现单元测试的教程
Apr 20 Python
Python中的迭代器与生成器高级用法解析
Jun 28 Python
python中常用的九种预处理方法分享
Sep 11 Python
python 根据正则表达式提取指定的内容实例详解
Dec 04 Python
python正则表达式的使用
Jun 12 Python
Python File readlines() 使用方法
Mar 19 Python
Python3爬虫学习之MySQL数据库存储爬取的信息详解
Dec 12 Python
python字符串分割及字符串的一些常规方法
Jul 24 Python
django ModelForm修改显示缩略图 imagefield类型的实例
Jul 28 Python
python中sqllite插入numpy数组到数据库的实现方法
Jun 21 Python
Python描述符descriptor使用原理解析
Mar 21 #Python
Python如何省略括号方法详解
Mar 21 #Python
Python如何使用bokeh包和geojson数据绘制地图
Mar 21 #Python
Spring Boot中使用IntelliJ IDEA插件EasyCode一键生成代码详细方法
Mar 20 #Python
python+opencv实现移动侦测(帧差法)
Mar 20 #Python
Java Spring项目国际化(i18n)详细方法与实例
Mar 20 #Python
Python 自由定制表格的实现示例
Mar 20 #Python
You might like
php下防止单引号,双引号在接受页面转义的设置方法
2008/09/25 PHP
phpmyadmin里面导入sql语句格式的大量数据的方法
2010/06/05 PHP
Windows下的PHP安装文件线程安全和非线程安全的区别
2014/04/23 PHP
Array.slice()与Array.splice()的返回值类型
2006/10/09 Javascript
JavaScript 处理Iframe自适应高度(同或不同域名下)
2013/03/29 Javascript
用Jquery重写windows.alert方法实现思路
2013/04/03 Javascript
JS继承--原型链继承和类式继承
2013/04/08 Javascript
js和css写一个可以自动隐藏的悬浮框
2014/03/05 Javascript
简单谈谈json跨域
2016/03/13 Javascript
jQuery使用$获取对象后检查该对象是否存在的实现方法
2016/09/04 Javascript
单击按钮发送验证码,出现倒计时的简单实例
2017/03/17 Javascript
Bootstrap警告(Alerts)的实现方法
2017/03/22 Javascript
nodejs的路径问题的解决
2018/06/30 NodeJs
详解JS取出两个数组中的不同或相同元素
2019/03/20 Javascript
[01:06:19]DOTA2-DPC中国联赛定级赛 LBZS vs SAG BO3第二场 1月8日
2021/03/11 DOTA
[01:16:50]DOTA2-DPC中国联赛 正赛 Phoenix vs CDEC BO3 第一场 3月7日
2021/03/11 DOTA
Python二叉搜索树与双向链表转换实现方法
2016/04/29 Python
Python中set与frozenset方法和区别详解
2016/05/23 Python
实例解析Python中的__new__特殊方法
2016/06/02 Python
Python使用自带的ConfigParser模块读写ini配置文件
2016/06/26 Python
详细解读tornado协程(coroutine)原理
2018/01/15 Python
opencv与numpy的图像基本操作
2019/03/08 Python
python获取点击的坐标画图形的方法
2019/07/09 Python
pytorch逐元素比较tensor大小实例
2020/01/03 Python
python模拟预测一下新型冠状病毒肺炎的数据
2020/02/01 Python
Python虚拟环境virtualenv创建及使用过程图解
2020/12/08 Python
HTML5 新事件 小结
2009/07/16 HTML / CSS
英国布鲁姆精品店:Bloom Boutique
2018/03/01 全球购物
日本动漫周边服饰销售网站:Atsuko
2019/12/16 全球购物
长曲棍球装备:Lacrosse Monkey
2020/12/02 全球购物
abstract class和interface有什么区别?
2012/01/03 面试题
在教室放鞭炮的检讨书
2014/09/28 职场文书
导游词400字
2015/02/13 职场文书
2016年寒假见闻
2015/10/10 职场文书
利用 SQL Server 过滤索引提高查询语句的性能分析
2021/07/15 SQL Server
高通2023 年将发布高性能PC处理器
2022/04/29 数码科技