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的Flask框架中使用模版的入门教程
Apr 20 Python
python 字典(dict)按键和值排序
Jun 28 Python
Python制作钉钉加密/解密工具
Dec 07 Python
Python 遍历子文件和所有子文件夹的代码实例
Dec 21 Python
Python获取当前路径实现代码
May 08 Python
Django通过dwebsocket实现websocket的例子
Nov 15 Python
Python面向对象之私有属性和私有方法应用案例分析
Dec 31 Python
Python自动巡检H3C交换机实现过程解析
Aug 14 Python
anaconda3安装及jupyter环境配置全教程
Aug 24 Python
详解torch.Tensor的4种乘法
Sep 03 Python
如何Tkinter模块编写Python图形界面
Oct 14 Python
Python实现学生管理系统(面向对象版)
Jun 24 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 过滤英文标点符号及过滤中文标点符号代码
2014/06/12 PHP
php实现表单多按钮提交action的处理方法
2015/10/24 PHP
php+mysql查询实现无限下级分类树输出示例
2016/10/03 PHP
php Session无效分析资料整理
2016/11/29 PHP
PHP 中TP5 Request 请求对象的实例详解
2017/07/31 PHP
详解PHP发送邮件知识点
2018/05/06 PHP
tp5.1 框架查询表达式用法详解
2020/05/25 PHP
js字符串的各种格式的转换 ToString,Format
2011/08/08 Javascript
file控件选择上传文件确定后触发的js事件是哪个
2014/03/17 Javascript
JavaScript字符串对象substring方法入门实例(用于截取字符串)
2014/10/17 Javascript
jQuery中toggleClass()方法用法实例
2015/01/05 Javascript
jQuery实现表单步骤流程导航代码分享
2015/08/28 Javascript
xcode中获取js文件的路径方法(推荐)
2016/11/05 Javascript
JS碰撞运动实现方法详解
2016/12/15 Javascript
jQuery布局组件EasyUI Layout使用方法详解
2017/02/28 Javascript
Angular实现的自定义模糊查询、排序及三角箭头标注功能示例
2017/12/28 Javascript
vue将时间戳转换成自定义时间格式的方法
2018/03/02 Javascript
在vue中使用jointjs的方法
2018/03/24 Javascript
JavaScript中arguments和this对象用法分析
2018/08/08 Javascript
js中null与空字符串&quot;&quot;的区别讲解
2019/01/17 Javascript
如何自定义微信小程序tabbar上边框的颜色
2019/07/09 Javascript
[07:27]DOTA2卡尔工作室 英雄介绍水晶室女篇
2013/06/21 DOTA
python通过shutil实现快速文件复制的方法
2015/03/14 Python
python面向对象_详谈类的继承与方法的重载
2017/06/07 Python
python如何生成各种随机分布图
2018/08/27 Python
python使用Matplotlib改变坐标轴的默认位置
2019/10/18 Python
Python优秀开源项目Rich源码解析的流程分析
2020/07/06 Python
Django后端按照日期查询的方法教程
2021/02/28 Python
使用CSS3实现圆角,阴影,透明
2014/12/23 HTML / CSS
如何查看浏览器对html5的支持情况
2020/12/15 HTML / CSS
Linux上比较文件的命令都有哪些
2012/02/24 面试题
物流业务员岗位职责
2014/02/08 职场文书
税务会计岗位职责
2014/02/18 职场文书
查摆问题整改措施范文
2014/10/11 职场文书
2015年机关党建工作总结
2015/05/22 职场文书
教师节座谈会主持词
2015/07/03 职场文书