数据类型#
另请参见
数组类型和类型之间的转换#
NumPy 支持比 Python 多得多的数值类型。本节介绍可用的数值类型以及如何修改数组的数据类型。
NumPy 数值类型是 numpy.dtype
(数据类型)对象的实例,每个对象都有其独特的特性。使用 import numpy as np
导入 NumPy 后,可以使用 numpy 顶级 API 中的标量类型(例如 numpy.bool
、numpy.float32
等)创建具有指定 dtype 的数组。
这些标量类型作为许多 numpy 函数或方法接受的 dtype 关键字的参数。例如
>>> z = np.arange(3, dtype=np.uint8)
>>> z
array([0, 1, 2], dtype=uint8)
数组类型也可以通过字符代码来引用,例如
>>> np.array([1, 2, 3], dtype='f')
array([1., 2., 3.], dtype=float32)
>>> np.array([1, 2, 3], dtype='d')
array([1., 2., 3.], dtype=float64)
有关指定和构造数据类型对象(包括如何指定字节序等参数)的更多信息,请参见 指定和构造数据类型。
要转换数组的类型,请使用 .astype() 方法。例如
>>> z.astype(np.float64)
array([0., 1., 2.])
请注意,在上面,我们可以使用 *Python* 浮点数对象作为 dtype,而不是 numpy.float64
。NumPy 知道 int
指的是 numpy.int_
,bool
指的是 numpy.bool
,float
是 numpy.float64
,而 complex
是 numpy.complex128
。其他数据类型没有 Python 等效项。
要确定数组的类型,请查看 dtype 属性
>>> z.dtype
dtype('uint8')
dtype 对象还包含有关类型的信息,例如其位宽和字节序。数据类型也可以间接用于查询类型的属性,例如它是否是整数
>>> d = np.dtype(np.int64)
>>> d
dtype('int64')
>>> np.issubdtype(d, np.integer)
True
>>> np.issubdtype(d, np.floating)
False
数值数据类型#
有 5 种基本数值类型,分别表示布尔值 (bool
)、整数 (int
)、无符号整数 (uint
)、浮点数 (float
) 和 complex
。基本数值类型名称与数值位大小相结合定义具体的类型。位大小是在内存中表示单个值所需的位数。例如,numpy.float64
是 64 位浮点数据类型。某些类型(例如 numpy.int_
和 numpy.intp
)的位大小不同,取决于平台(例如 32 位与 64 位 CPU 架构)。在与低级代码(例如 C 或 Fortran)交互时,应考虑到这一点,因为在这些代码中会直接访问原始内存。
字符串和字节的数据类型#
除了数值类型外,NumPy 还支持通过 numpy.str_
dtype (U
字符代码) 存储 Unicode 字符串,通过 numpy.bytes_
(S
字符代码) 存储以 null 结尾的字节序列,以及通过 numpy.void
(V
字符代码) 存储任意字节序列。
以上所有类型都是 *固定宽度* 数据类型。它们由宽度参数化(以字节或 Unicode 代码点表示),数组中的单个数据元素必须适合此宽度。这意味着使用此 dtype 存储字节序列或字符串数组需要预先知道或计算最长文本或字节序列的大小。
例如,我们可以创建一个数组来存储单词 "hello"
和 "world!"
>>> np.array(["hello", "world!"])
array(['hello', 'world!'], dtype='<U6')
这里的数据类型被检测为最多 6 个代码点的 Unicode 字符串,足以存储两个条目而不会截断。如果我们指定较短或较长的数据类型,则字符串会被截断或用零填充以适应指定的宽度。
>>> np.array(["hello", "world!"], dtype="U5")
array(['hello', 'world'], dtype='<U5')
>>> np.array(["hello", "world!"], dtype="U7")
array(['hello', 'world!'], dtype='<U7')
如果我们使用字节数据类型并要求 NumPy 打印出数组缓冲区中的字节,我们可以更清楚地看到零填充。
>>> np.array(["hello", "world"], dtype="S7").tobytes()
b'hello\x00\x00world\x00\x00'
每个条目都用两个额外的空字节填充。但是请注意,NumPy 无法区分故意存储的尾随空字节和填充空字节。
>>> x = [b"hello\0\0", b"world"]
>>> a = np.array(x, dtype="S7")
>>> print(a[0])
b"hello"
>>> a[0] == x[0]
False
如果需要存储和往返任何尾随空字节,则需要使用非结构化 void 数据类型。
>>> a = np.array(x, dtype="V7")
>>> a
array([b'\x68\x65\x6C\x6C\x6F\x00\x00', b'\x77\x6F\x72\x6C\x64\x00\x00'],
dtype='|V7')
>>> a[0] == np.void(x[0])
True
高级类型(上面未列出)在 结构化数组 部分进行了探讨。
NumPy 数据类型和 C 数据类型之间的关系#
NumPy 提供基于位大小的类型名称和基于 C 类型名称的名称。由于 C 类型的定义是依赖于平台的,这意味着应优先使用明确的位大小类型,以避免在使用 NumPy 的程序中出现依赖于平台的行为。
为了方便与 C 代码集成(在 C 代码中,引用依赖于平台的 C 类型更自然),NumPy 还提供了与平台的 C 类型对应的类型别名。一些 dtype 带有尾随下划线,以避免与内置 python 类型名称混淆,例如 numpy.bool_
。
规范的 Python API 名称 |
Python API 的“C 风格”名称 |
实际的 C 类型 |
描述 |
---|---|---|---|
N/A |
|
布尔值(True 或 False),存储为一个字节。 |
|
|
具有 8 位的平台定义整数类型。 |
||
|
具有 8 位的无符号平台定义整数类型。 |
||
|
具有 16 位的平台定义整数类型。 |
||
|
具有 16 位的无符号平台定义整数类型。 |
||
|
具有 32 位的平台定义整数类型。 |
||
|
具有 32 位的无符号平台定义整数类型。 |
||
N/A |
|
大小为 |
|
N/A |
|
能够存储最大分配大小的平台定义整数类型。 |
|
N/A |
|
|
保证可以容纳指针。仅字符代码(Python 和 C)。 |
N/A |
|
|
保证可以容纳指针。仅字符代码(Python 和 C)。 |
|
至少具有 32 位的平台定义整数类型。 |
||
|
至少具有 32 位的无符号平台定义整数类型。 |
||
N/A |
|
至少具有 64 位的平台定义整数类型。 |
|
N/A |
|
至少具有 64 位的无符号平台定义整数类型。 |
|
N/A |
半精度浮点数:符号位、5 位指数、10 位尾数。 |
||
|
平台定义的单精度浮点数:通常包含符号位、8位指数和23位尾数。 |
||
|
平台定义的双精度浮点数:通常包含符号位、11位指数和52位尾数。 |
||
|
|
平台定义的扩展精度浮点数。 |
|
|
复数,由两个单精度浮点数表示(实部和虚部)。 |
||
|
复数,由两个双精度浮点数表示(实部和虚部)。 |
||
|
|
复数,由两个扩展精度浮点数表示(实部和虚部)。 |
由于许多类型的定义都依赖于平台,因此提供了一组固定大小的别名(参见 固定大小的别名)。
数组标量#
NumPy 通常将数组的元素作为数组标量返回(具有关联 dtype 的标量)。数组标量与 Python 标量不同,但在大多数情况下可以互换使用(主要例外是 Python v2.x 之前的版本,其中整数数组标量不能用作列表和元组的索引)。有一些例外情况,例如当代码需要标量的非常具体的属性,或者当它专门检查一个值是否为 Python 标量时。通常,可以通过使用相应的 Python 类型函数(例如,int
、float
、complex
、str
)显式地将数组标量转换为 Python 标量来轻松修复问题。
使用数组标量的主要优点是它保留了数组类型(Python 可能没有可用的匹配标量类型,例如 int16
)。因此,使用数组标量确保了数组和标量之间相同的行为,无论值是在数组内还是不在数组内。NumPy 标量也具有许多与数组相同的 method。
溢出错误#
NumPy 数值类型的固定大小可能会在值需要的内存超过数据类型中可用内存时导致溢出错误。例如,numpy.power
正确计算 64 位整数的 100 ** 9
,但对于 32 位整数则给出 -1486618624(不正确)。
>>> np.power(100, 9, dtype=np.int64)
1000000000000000000
>>> np.power(100, 9, dtype=np.int32)
np.int32(-1486618624)
对于整数溢出,NumPy 和 Python 整数类型的行为差异很大,可能会让期望 NumPy 整数的行为类似于 Python 的 int
的用户感到困惑。与 NumPy 不同,Python 的 int
的大小是灵活的。这意味着 Python 整数可以扩展以容纳任何整数,并且不会溢出。
NumPy 提供 numpy.iinfo
和 numpy.finfo
分别用于验证 NumPy 整数和浮点数的最小值或最大值。
>>> np.iinfo(int) # Bounds of the default integer on this system.
iinfo(min=-9223372036854775808, max=9223372036854775807, dtype=int64)
>>> np.iinfo(np.int32) # Bounds of a 32-bit integer
iinfo(min=-2147483648, max=2147483647, dtype=int32)
>>> np.iinfo(np.int64) # Bounds of a 64-bit integer
iinfo(min=-9223372036854775808, max=9223372036854775807, dtype=int64)
如果 64 位整数仍然太小,结果可能会转换为浮点数。浮点数提供了更大但不太精确的可能值范围。
>>> np.power(100, 100, dtype=np.int64) # Incorrect even with 64-bit int
0
>>> np.power(100, 100, dtype=np.float64)
1e+200
浮点精度#
NumPy 中的许多函数,特别是 numpy.linalg
中的函数,都涉及浮点运算,由于计算机表示十进制数字的方式,这可能会引入微小的不准确性。例如,在执行涉及浮点数的基本算术运算时
>>> 0.3 - 0.2 - 0.1 # This does not equal 0 due to floating-point precision
-2.7755575615628914e-17
为了处理这种情况,建议使用诸如 np.isclose 之类的函数来比较值,而不是检查精确相等性。
>>> np.isclose(0.3 - 0.2 - 0.1, 0, rtol=1e-05) # Check for closeness to 0
True
在这个例子中,np.isclose 通过应用相对容差来解释浮点计算中发生的微小不准确性,确保在小阈值内的结果被认为是接近的。
有关计算精度的信息,请参见 浮点运算。
扩展精度#
Python 的浮点数通常是 64 位浮点数,几乎等效于 numpy.float64
。在某些不寻常的情况下,使用更高精度的浮点数可能很有用。这在 NumPy 中是否可行取决于硬件和开发环境:具体而言,x86 机器提供具有 80 位精度的硬件浮点,虽然大多数 C 编译器将其作为 long double
类型提供,但 MSVC(Windows 构建的标准)使 long double
与 double
(64 位) 相同。NumPy 将编译器的 long double
作为 numpy.longdouble
(以及复数的 np.clongdouble
)提供。您可以使用 np.finfo(np.longdouble)
找出您的 NumPy 提供了什么。
NumPy 没有提供比 C 的 long double
更高精度的 dtype;特别是,128 位 IEEE 四精度数据类型 (FORTRAN 的 REAL*16
) 是不可用的。
为了高效的内存对齐,numpy.longdouble
通常以零位填充的方式存储,填充到 96 位或 128 位。哪种方式更高效取决于硬件和开发环境;通常在 32 位系统上,它们填充到 96 位,而在 64 位系统上,它们通常填充到 128 位。np.longdouble
填充到系统默认值;np.float96
和 np.float128
是为需要特定填充的用户提供的。尽管名称如此,np.float96
和 np.float128
只提供与 np.longdouble
一样多的精度,即在大多数 x86 机器上为 80 位,在标准 Windows 构建中为 64 位。
需要注意的是,即使 numpy.longdouble
提供比 python float
更高的精度,也很容易丢失额外的精度,因为 python 经常强制值通过 float
。例如,%
格式化运算符要求其参数转换为标准 python 类型,因此即使请求许多小数位,也不可能保留扩展精度。使用值 1 + np.finfo(np.longdouble).eps
测试您的代码可能很有用。