数据类型#

数组类型和类型之间的转换#

NumPy支持比Python多得多的数值类型. 本节介绍哪些可用,以及如何修改数组的数据类型.

NumPy 数值类型是 numpy.dtype (数据类型) 对象的实例,每个对象都有其独特的特性.一旦你使用 import numpy as np 导入 NumPy,你就可以使用 numpy 顶层 API 中的标量类型创建具有指定 dtype 的数组,例如 numpy.bool , numpy.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_ , bool 表示 numpy.bool , 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

如果您需要存储和往返任何尾随空字节,您将需要使用非结构化的 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 类型的类型别名.一些 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

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

由于其中许多具有平台相关的定义,因此提供了一组固定大小的别名(参见 大小别名 ).

数组标量#

NumPy 通常将数组的元素作为数组标量返回(具有关联 dtype 的标量).数组标量不同于 Python 标量,但在大多数情况下,它们可以互换使用(主要的例外是低于 v2.x 的 Python 版本,其中整数数组标量不能用作列表和元组的索引).在某些情况下,例如当代码需要标量的非常具体的属性或当它专门检查一个值是否为 Python 标量时,会存在一些例外.通常,通过使用相应的 Python 类型函数(例如, int , float , complex , str )将数组标量显式转换为 Python 标量,可以很容易地解决这些问题.

使用数组标量的主要优点是它们保留了数组类型(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

浮点精度#

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 通过应用相对容差来解决浮点计算中发生的微小不准确性,确保小阈值范围内的结果被认为是接近的.

有关计算精度的信息,请参阅 Floating-Point Arithmetic .

扩展精度#

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 的 REAL16 )不可用.

为了有效地进行内存对齐, numpy.longdouble 通常存储时会填充零位,填充到 96 位或 128 位.哪一个效率更高取决于硬件和开发环境;通常在 32 位系统上,它们被填充到 96 位,而在 64 位系统上,它们通常被填充到 128 位. np.longdouble 填充到系统默认值; np.float96np.float128 提供给想要特定填充的用户. 尽管有这些名称, np.float96np.float128 提供的精度与 np.longdouble 相同,即在大多数 x86 机器上为 80 位,在标准 Windows 版本中为 64 位.

请注意,即使 numpy.longdouble 比 python float 提供更高的精度,也很容易丢失额外的精度,因为 python 经常强制值通过 float 传递.例如, % 格式化运算符要求将其参数转换为标准 python 类型,因此即使请求许多小数位,也不可能保留扩展精度.使用值 1 + np.finfo(np.longdouble).eps 测试您的代码可能会很有用.