数据类型#

数组类型及其类型之间的转换#

NumPy 支持比 Python 更多种类的数值类型.本节展示哪些类型是可用的,以及如何修改数组的数据类型.

NumPy 数值类型是 numpy.dtype (数据类型) 对象的实例,每个都具有独特的特性.一旦你使用 import numpy as np 导入了 NumPy,你可以使用 numpy 顶层 API 中的标量类型创建具有指定 dtype 的数组,例如 numpy.boolnumpy.float32 等.

这些标量类型作为 dtype 关键字参数,许多 numpy 函数或方法都接受.例如:

>>> 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 float 对象作为 dtype 而不是 numpy.float64.NumPy 知道 int 指的是 numpy.int_,:class:bool 意味着 numpy.bool,:class:floatnumpy.float64,而 complexnumpy.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` 字符代码)存储以空字符结尾的字节序列,以及通过 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

如果你需要存储并往返任何尾随的空字节,你需要使用一个非结构化的空数据类型:

>>> 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类型,NumPy还提供了与平台C类型对应的类型别名.一些dtypes带有下划线,以避免与内置的Python类型名称混淆,例如 numpy.bool_.

规范的 Python API 名称

Python API “类C” 名称

实际的 C 类型

描述

numpy.boolnumpy.bool_

N/A

bool (定义在 stdbool.h 中)

布尔值(True 或 False)存储为一个字节.

numpy.int8

numpy.byte

signed char

定义为8位整数类型的平台.

numpy.uint8

numpy.ubyte

unsigned char

平台定义的无符号8位整数类型.

numpy.int16

numpy.short

short

定义为16位的平台整数类型.

numpy.uint16

numpy.ushort

unsigned short

平台定义的无符号16位整数类型.

numpy.int32

numpy.intc

int

定义为32位整数类型的平台.

numpy.uint32

numpy.uintc

unsigned int

平台定义的无符号32位整数类型.

numpy.intp

N/A

ssize_t/Py_ssize_t

平台定义的 size_t 大小的整数;例如用于大小.

numpy.uintp

N/A

size_t

平台定义的整数类型,能够存储最大分配大小.

N/A

'p'

intptr_t

保证持有指针.仅限字符代码(Python 和 C).

N/A

'P'

uintptr_t

保证持有指针.仅限字符代码(Python 和 C).

numpy.int32numpy.int64

numpy.long

long

至少有32位的平台定义的整数类型.

numpy.uint32numpy.uint64

numpy.ulong

unsigned long

平台定义的无符号至少32位的整数类型.

N/A

numpy.longlong

long long

至少有64位的平台定义整数类型.

N/A

numpy.ulonglong

unsigned long long

平台定义的无符号至少64位的整数类型.

numpy.float16

numpy.half

N/A

半精度浮点数:符号位,5位指数,10位尾数.

numpy.float32

numpy.single

float

平台定义的单精度浮点数:通常包括符号位、8位指数和23位尾数.

numpy.float64

numpy.double

double

平台定义的双精度浮点数:通常包括符号位、11位指数和52位尾数.

numpy.float96numpy.float128

numpy.longdouble

long double

平台定义的扩展精度浮点数.

numpy.complex64

numpy.csingle

float complex

复数,由两个单精度浮点数表示(实部和虚部).

numpy.complex128

numpy.cdouble

double complex

复数,由两个双精度浮点数表示(实部和虚部).

numpy.complex192numpy.complex256

numpy.clongdouble

long double complex

复数,由两个扩展精度的浮点数表示(实部和虚部).

由于许多这些定义依赖于平台,因此提供了一组固定大小的别名(参见 Sized 别名).

数组标量#

NumPy 通常返回数组元素作为数组标量(带有相关 dtype 的标量).数组标量与 Python 标量不同,但在大多数情况下它们可以互换使用(主要例外是对于早于 v2.x 版本的 Python,其中整数数组标量不能作为列表和元组的索引).有一些例外情况,例如当代码需要标量的非常特定的属性时,或者当它特别检查一个值是否是 Python 标量时.通常,问题可以通过显式地将数组标量转换为 Python 标量来轻松解决,使用相应的 Python 类型函数(例如,:class:int, float, complex, str).

使用数组标量的主要优势是它们保留了数组类型(Python 可能没有可用的匹配标量类型,例如 int16).因此,使用数组标量确保了数组和标量之间的行为一致,无论值是否在数组内部.NumPy 标量也有很多与数组相同的方法.

溢出错误#

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.iinfonumpy.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

扩展精度#

Python 的浮点数通常是 64 位的浮点数,几乎等同于 numpy.float64.在某些不寻常的情况下,使用精度更高的浮点数可能会有用.这在 numpy 中是否可行取决于硬件和开发环境:具体来说,x86 机器提供 80 位精度的硬件浮点数,虽然大多数 C 编译器将其作为它们的 long double 类型提供,但 MSVC(用于 Windows 构建的标准编译器)使 long doubledouble``(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 测试代码可能会很有用.