Python小整数对象池和字符串intern实例解析
is用于判断两个对象是否为同一个对象,具体来说是两个对象在内存中的位置是否相同。
python为了提高效率,节省内存,在实现上大量使用了缓冲池技术和字符串intern技术。
整数和字符串是不可变对象,也就意味着可以用来共享,如100个“python”字串变量可以共享一个“python”字符串对象,而不是创建100个“python”字符串。
小整数对象池
为了应对小整数的频繁使用,python使用对小整数进行了缓存,默认范围为[-5,256],在这个范围内的所有整数被python完全地缓存,当有变量使用这些小整数时,增加对应小整数对象的引用即可。
>>>i=-5 >>>j=-5 >>>iisj#i和j是同一个对象 True >>>i=256 >>>j=256 >>>iisj#i和j是同一个对象 True >>>i=257 >>>j=257 >>>iisj#i和j是不同对象 False
由上面的实例可以看到,当变量在[-5,256]之间时,两个值相同的变量事实上会引用到同一个小整数对象上,也就是小整数对象池中的对象,而不会去创建两个对象。而当变量超出了这个范围,两个值相同的变量也会各自创建整数对象,所以两者对应的对象不同。
字符串intern
如果当前变量引用的字符串对象已经存在的话,直接增加对应字符串对象的引用,而不去创建新的字符串对象,这就是字符串intern机制。
>>>i="12"
>>>j="12"
>>>iisj
True
在详细探讨字符串intern机制之前,先看一个奇怪的问题:
>>>i="12"
>>>j="12"
>>>iisj
False
i="12"
j="12"
print(iisj)
输出结果
True
上述代码分开运行,结果为False,但是合在一起结果却为True,也就是说分开运行的时候,i,j指向不同对象,而合在一起的时候i,j却指向了相同对象。为了明白其中的缘由,需要简单理解python的编译机制。
编译机制
在python中,万物皆对象,包括代码本身也是一种对象。python用code对象表示代码,代码编译后产生code对象。通常一个作用域对应一个code对象。
i="12" j="12" print(iisj) deff(): pass
编译结果
20LOAD_CONST0('12')
2STORE_NAME0(i)34LOAD_CONST0('12')
6STORE_NAME1(j)58LOAD_CONST1(
)
10LOAD_CONST2('f')
12MAKE_FUNCTION0
14STORE_NAME2(f)
16LOAD_CONST3(None)
18RETURN_VALUEDisassemblyof
:
60LOAD_CONST0(None)
2RETURN_VALUE
上述代码中编译生成了两个code对象,一个代表全局作用域,另一个代表函数f。
code对象保存了变量,常量(常量字面量)以及编译结果。code对象用常量表来保存常量,考虑到一个常量可能出现多次,在一张表上保存一个常量多次太过于奢侈。所以code对象对每个常量只保存一次,在需要引用它的地方使用它在常量表的位置作为常量的表示。在上述编译结果中可以看到,"12"这个字符串常量使用了两次,编译的代码为"LOAD_CONST0",这里的0就是"12"在常量表当中的位置。
由于编译的这个特性,在同一个code对象中的变量,如果它们引用了同一个常量,那么无论这个常量有没有缓冲机制,它们引用的都是同一个对象。
a="12" b="12" c="12" d="12" e=257 f=257 g=2424234234234234 h=2424234234234234 print(aisb,cisd,eisf,gish)
输出结果
TrueTrueTrueTrue
这个例子说明,在同一个code对象当中,常量(字面量)仅一份,这与缓冲机制无关,是编译特性。所以对于上述那个奇怪的问题就可以解释了,当i,j在同一个code对象(同一个作用域)中引用常量"12",它们引用的都是同一个对象。而当在python命令行中分开执行时,对于每一条语句,都是一个单独的code对象,这时起作用的是字符串intern机制,上述运行结果说明,字符串intern机制对"12"进行了intern,而对"12"没有进行intern。
编译机制与小整数对象池对比
i=257 j=257 a=i-1 b=i-1 c=i+1 d=i+1 print(iisj,aisb,cisd)
输出结果
TrueTrueFalse
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(aisb,cisd)
输出结果
bbb
TrueFalse
可以看到,a和b确实指向同一个对象,而c和d指向不同对象,这就是字符缓冲池。
编译机制与字符串intern对比
i="12" j="12" k="__fjdslfjaskfas" ii="12" jj="12" kk="__fjdslfjaskfas" deff(): a="12" b="12" c="__fjdslfjaskfas" returnaisi,bisj,cisk print("Code:",iisii,jisjj,kiskk) print(f"intern:{f()}")
输出结果
Code:TrueTrueTrue
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缓存小的整数和字符,当有变量使用这些对象时,不用额外创建对象。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。