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库urllib与urllib2主要区别分析
Jul 13 Python
python生成器generator用法实例分析
Jun 04 Python
Python聚类算法之基本K均值实例详解
Nov 20 Python
python队列通信:rabbitMQ的使用(实例讲解)
Dec 22 Python
Python实现的拟合二元一次函数功能示例【基于scipy模块】
May 15 Python
Python将一个Excel拆分为多个Excel
Nov 07 Python
python matplotlib画图库学习绘制常用的图
Mar 19 Python
Python3.5面向对象程序设计之类的继承和多态详解
Apr 24 Python
Python实现基于SVM的分类器的方法
Jul 19 Python
python PIL/cv2/base64相互转换实例
Jan 09 Python
Django全局启用登陆验证login_required的方法
Jun 02 Python
解决Python 写文件报错TypeError的问题
Oct 23 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中数组定义的几种方法
2013/09/01 PHP
php下载文件超时时间的设置方法
2016/10/06 PHP
PHP基于ip2long实现IP转换整形
2020/12/11 PHP
C#中TrimStart,TrimEnd,Trim在javascript上的实现
2011/01/17 Javascript
javascript中的对象创建 实例附注释
2011/02/08 Javascript
Jquery 跨域访问 Lightswitch OData Service的方法
2013/09/11 Javascript
js字符串转换成数字与数字转换成字符串的实现方法
2014/01/08 Javascript
js子页面获取父页面数据示例
2014/05/15 Javascript
JS实现设置ff与ie元素绝对位置的方法
2016/03/08 Javascript
高效Web开发的10个jQuery代码片段
2016/07/22 Javascript
利用jQuery插件imgAreaSelect实现图片上传裁剪(同步显示图像位置信息)
2016/12/02 Javascript
详解Nodejs基于mongoose模块的增删改查的操作
2016/12/21 NodeJs
MUI 上拉刷新/下拉加载功能实例代码
2017/04/13 Javascript
Vue的路由动态重定向和导航守卫实例
2018/03/17 Javascript
vue.js中proxyTable 转发请求的实现方法
2018/09/20 Javascript
详解如何创建并发布一个 vue 组件
2018/11/08 Javascript
vue强制刷新组件的方法示例
2019/02/28 Javascript
Koa从零搭建到Api实现项目的搭建方法
2019/07/30 Javascript
[41:52]2018DOTA2亚洲邀请赛3月29日小组赛B组Effect VS Secret
2018/03/30 DOTA
python冒泡排序算法的实现代码
2013/11/21 Python
python实现同时给多个变量赋值的方法
2015/04/30 Python
python matplotlib中文显示参数设置解析
2017/12/15 Python
使用 Python 遍历目录树的方法
2020/02/29 Python
python多线程实现同时执行两个while循环的操作
2020/05/02 Python
详解python安装matplotlib库三种失败情况
2020/07/28 Python
CSS3 实现时间轴动画
2020/11/25 HTML / CSS
生日邀请函范文
2014/01/13 职场文书
《金钱的魔力》教学反思
2014/02/24 职场文书
家长给学校的建议书
2014/05/15 职场文书
消防安全宣传口号
2014/06/10 职场文书
2014年专项整治工作总结
2014/11/17 职场文书
2015年校长新年寄语
2014/12/08 职场文书
给老师的保证书怎么写
2015/05/09 职场文书
公司员工手册范本
2015/05/14 职场文书
企业管理制度设计时要注意的几种“常见病”!
2019/04/19 职场文书
新的CSS 伪类函数 :is() 和 :where()示例详解
2022/08/05 HTML / CSS