对于常用的小数字,解释器会在初始化时进行预缓存。稍后使用,直接将名字关联到这些缓存对象即可。如此一来,无须创建实例对象,可提高性能,且节约内存开销。
Python 3.6 预缓存范围是[-5, 256]。
>>> a = -5
>>> b = -5
>>> a is b
True
>>> a = 256
>>> b = 256
>>> a is b
True
如果超出缓存范围,那么每次都要新建对象,这其中包括内存分配等操作。
>>> a = -6
>>> b = -6
>>> a is b
False
>>> a = 257
>>> b = 257
>>> a is b
False
开发大数据等应用,免不了持有海量数字对象(比如超大ID 列表)。Python 2 对回收后的整数复用内存不做收缩处理,会导致大量闲置内存驻留。而Python 3 则改进了此设计,极大地减少了内存占用,这也算是迁移到新版的一个理由。
from __future__ import print_function
import psutil
def rss(): # 输出进程RSS 内存大小
m = psutil.Process().memory_info()
print(m.rss >> 20, "MB")
if __name__ == '__main__':
rss() # 起始内存占用
x = list(range(10000000)) # 使用列表持有海量数字对象
rss() # 输出海量数字造成的内存开销
del x # 删除列表,回收数字对象
rss() # 输出回收后的内存占用
输出:
$ python2.7 test.py
11 MB
323 MB
246 MB
$ python3.6 test.py
11 MB
397 MB
11 MB
默认float 类型存储双精度(double)浮点数,可表达16 到17 个小数位。
>>> 1/3
0.3333333333333333
>>> 0.1234567890123456789
0.12345678901234568
从实现方式看,浮点数以二进制存储十进制数的近似值。这可能导致执行结果与编码预期不符,造成不一致性缺陷。所以,对精度有严格要求的场合,应选择固定精度类型。
可通过 float.hex 方法输出实际存储值的十六进制格式字符串,以检查执行结果为何不同。
还可用该方式实现浮点值的精确传递,避免精度丢失。
>>> 0.1 * 3 == 0.3
False
>>> (0.1 * 3).hex() # 显然两个存储内容并不相同
'0x1.3333333333334p-2'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> s = (1/3).hex()
>>> float.fromhex(s) # 反向转换回浮点数
0.3333333333333333
对于简单的比较操作,可尝试将浮点数限制在有效的固定精度内。但考虑到round 算法实现问题,更准确的做法是用decimal.Decimal 类型。
>>> round(0.1 * 3, 2) == round(0.3, 2) # 避免不确定性,左右都使用固定精度
True
>>> round(0.1, 2) * 3 == round(0.3, 2) # 将 round 返回值作为操作数,导致精度再次丢失
False
不同类型的数字之间,可直接进行加减和比较运算。
>>> 1.1 + 2
3.1
>>> 1.1 < 2
True
>>> 1.1 == 1
False