许多编程语言区分整数、字节和长整数,有些编程语言还存在有符号整数和无符号整数的区别。如何将这些概念与Python联系起来呢?
答案是“不需要”。Python以统一的方式处理所有类型的整数。对于Python,从几个字节到数百位的巨大数字,都是整数。

准备工作

假设我们需要计算一些非常大的数字,例如,计算一副52张的扑克牌的排列数。52! = 52 × 51 × 50 × … × 2 × 1,这是一个非常大的数字。可以在Python中实现这个运算吗?

实战演练

别担心!Python表现得好像有一个通用的整数类型,涵盖了所有整数,从几个字节到填满所有内存的整数。正确使用整数的步骤如下。
(1) 写下你需要的数字,比如一些小数字:355,113。实际上,数字的大小没有上限。
(2) 创建一个非常小的值——单个字节,如下所示:

  1. >>> 2
  2. 2

或者使用十六进制:

>>> 0xff
2
>>> b'\xfe'
b'\xfe'

严格说来,这不是一个整数。它有一个前缀b’,这表明它是一个一字节序列(1-byte sequence)。
(3) 通过计算创建一个很大的数字。例如:

>>> 2 ** 2048
323...656

该数字有617个数位,这里并没有完全显示。

工作原理

Python内部使用两种数字,两者之间的转换是无缝且自动的。
对于较小的数字,Python通常使用4字节或8字节的整数值。细节隐藏在CPython的内核中,并依赖于构建Python的C编译器。
对于超出sys.maxsize的较大数字,Python将其切换到大整数——数字(digit)序列。在这种情况下,一位数字通常意味着30位(bit)的值。
一副52张的扑克牌有多少种排列方法?答案是? 我们将使用math模块的factorial函数计算这个大整数,如下所示:

>>> import math
>>> math.factorial(52)
80658175170943878571660636856403766975289505440883277824000000000000

计算52! 的第一部分(从52 × 51 × 50 × …一直到约42)可以完全使用较小的整数来执行。在此之后,其余的计算必须切换到大整数。我们看不到切换过程,只能看到结果。

通过下面的示例可以了解整数内部的一些细节。

>>> import sys
>>> import math
>>> math.log(sys.maxsize, 2)
63.0
>>> sys.int_info
sys.int_info(bits_per_digit = 30, sizeof_digit = 4)

sys.maxsize的值是小整数中的最大值。我们通过计算以2为底的对数来说明这个数字需要多少位。
通过计算可知,Python使用63位值来表示小整数。小整数的范围是从到。在此范围之外,使用大整数。
通过sys.int_info的值可知,大整数是使用30位的数字序列,每个数字占用4字节。

像52! 这样比较大的值,由8个上述30位的数字组成。一个数字需要30位来表示可能有些令人困惑。以用10个符号表示十进制(base 10)的数字为例,我们需要2**30个不同的符号来表示这些大数字的每位数。

涉及多个大整数值的计算可能会消耗相当大的内存空间。小数字怎么办呢? Python如何跟踪大量的小数字,如1和0?

对于常用的数字(-5到256),Python实际上创建了一个私有的对象池来优化内存管理。你可以在检查整数对象的id()值时得到验证。

>>> id(1)
4297537952
>>> id(2)
4297537984
>>> a = 1 + 1
>>> id(a)
4297537984

我们显示了整数1和整数2的内部id。当计算a的值时,结果对象与对象池中的整数2对象是同一个对象。

当你练习这个示例时,id()值可能跟示例不同。但是,在每次使用值2时,将使用相同的对象。在我的笔记本电脑上,对象2的id等于4297537984。这种机制避免了在内存里大量存放对象2的副本。

这里有个小技巧,可以看出一个数字到底有多大。

>>> len(str(2 ** 2048))
617

通过一个计算得到的数字创建一个字符串,然后查询字符串的长度。结果表明,这个数字有617个数位。