A Simple File Format for NumPy Arrays

Author: Robert Kern <robert.kern@gmail.com> Status: Draft Created: 20-Dec-2007

Abstract

我们建议一个标准的二进制文件格式(NPY),用于在磁盘上持久保存一个任意的NumPy数组。该格式存储所有的形状和dtype信息,即使在具有不同架构的另一台机器上,也可以正确重建数组。格式被设计为尽可能简单,同时实现其有限的目标。该实现旨在是纯Python并作为主numpy包的一部分分发。

Rationale

一个轻量级的,无所不在的系统,用于将NumPy数组保存到磁盘是一个常见的需要。Python通常有pickle [1]将大多数Python对象保存到磁盘。这通常工作得很好,NumPy数组用于许多目的,但它有一些缺点:

  • 倾倒或加载pickle文件需要在内存中复制数据。对于大数组,这可以是一个showstopper。
  • 数组数据不能通过内存映射直接访问。现在numpy具有这种能力,它已被证明非常有用的加载大量的数据(或更多的点:避免加载大量的数据,当你只需要一小部分)。

这两个问题都可以通过使用ndarray.tofile()和numpy.fromfile()将原始字节转储到磁盘来解决。但是,这些都有自己的问题:

  • 所写入的数据没有关于数组的形状或类型的信息。
  • 它不能处理对象数组。

NPY文件格式是这两种方法的进化前进。它的设计主要限于解决pickles和tofile()/ fromfile()的问题。它不打算解决更复杂的问题,其中更复杂的格式,如HDF5 [2]是一个更好的解决方案。

Use Cases

  • Neville Newbie刚刚开始收拾Python和NumPy。他没有安装许多软件包,但是,也没有学习标准库,但是他已经在交互式提示下使用NumPy来执行小任务。他得到一个他想要保存的结果。
  • Annie Analyst一直使用大型嵌套记录数组来表示她的统计数据。她想说服她使用R的同事David Doubter,Python和NumPy通过发送给她她的分析代码和数据是真棒。她需要以交互式速度加载数据。由于David通常不使用Python,因此需要安装大型软件包会把他关闭。
  • 西蒙地震学家正在开发新的地震处理工具。他的一个算法需要大量的中间数据写入磁盘。数据不真正适合行业标准的SEG-Y模式,但他已经有一个很好的记录数组dtype在内部使用它。
  • Polly Parallel希望尽可能简单地在她的多核机器上分割计算。计算的部分可以在不同过程之间分割,而在过程之间没有任何通信;他们只需要填充大数组的适当部分及其结果。有几个子进程内存映射公共数组是一个很好的方法来实现这一点。

Requirements

格式必须能够:

  • 表示所有NumPy数组,包括嵌套记录数组和对象数组。
  • 以其原生二进制形式表示数据。
  • 包含在单个文件中。
  • 直接支持Fortran-contiguous数组。
  • 存储所有必要的信息以在不同架构的机器上重建数组,包括形状和dtype。必须支持小端序和大端序数组,并且具有小端序号的文件将在读取文件的任何机器上产生小端序数组。类型必须根据其实际大小来描述。例如,如果具有64位C“long int”的机器写出具有“long ints”的数组,则具有32位C“long int”的读取机将产生具有64位整数的数组。
  • 反向工程。数据集通常比创建它们的程序寿命更长。一个有能力的开发人员应该能够以他的首选编程语言创建一个解决方案,以读取大多数NPY文件,而没有多少文档。
  • 允许数据的内存映射。
  • 从类似于文件的流对象读取,而不是实际的文件。这允许容易地测试实现并且使得系统更灵活。NPY文件可以存储在ZIP文件中,并可以从ZipFile对象轻松读取。
  • 存储对象数组。由于一般的Python对象是复杂的,只能通过pickle(如果有的话)可靠地序列化,对于包含对象数组的文件,可以放弃许多其他需求。具有对象数组的文件不必是mmapable,因为这在技术上是不可能的。我们不能期望pickle格式被反向工程,没有pickle的知识。但是,至少应该能够使用与其他数组相同的通用接口来读取和写入对象数组。
  • 使用numpy软件包中提供的API读取和写入,而不使用任何其他库。如果需要,numpy中的实现可以在C中。

明确的格式不需要

  • 在文件中支持多个数组。因为我们需要支持文件状对象,所以可以使用API​​来构建支持多个数组的ad hoc格式。然而,解决一般问题和用例超出了numpy的格式和API的范围。
  • 完全处理numpy.ndarray的任意子类。子类将被接受写入,但只有数组数据将被写出。读取文件时将创建常规numpy.ndarray对象。该API可用于为特定子类构建格式,但这超出了一般NPY格式的范围。

Format Specification: Version 1.0

前6个字节是一个魔术字符串:正好是“x93NUMPY”。

下一个1字节是无符号字节:文件格式的主要版本号,例如。 x01。

下一个1字节是无符号字节:文件格式的次要版本号,例如。 x00。注意:文件格式的版本不绑定到numpy软件包的版本。

接下来的2个字节形成一个little-endian unsigned short int:头数据HEADER_LEN的长度。

下一个HEADER_LEN字节形成描述数组格式的标题数据。它是一个包含字典的Python字面表达式的ASCII字符串。它由换行符('n')终止,并用空格('x20')填充,使得魔术字符串的总长度+ 4 + HEADER_LEN为了对齐的目的可以被16整除。

字典包含三个键:

“descr”
dtype.descr
可以作为参数传递给numpy.dtype()构造函数以创建数组的dtype的对象。
“fortran_order”
bool
数组数据是否是Fortran连续的。由于Fortran连续数组是非C连续性的常见形式,因此我们允许将它们直接写入磁盘以提高效率。
“形状”
int的元组
数组的形状。

为了重复性和可读性,此字典使用pprint.pformat()格式化,因此键是按字母顺序排列的。

数据组头后面是数组数据。如果dtype包含Python对象(即dtype.hasobject为True),则数据是数组的Python pickle。否则,数据是数组的连续(C或Fortran-,取决于fortran_order)字节。消费者可以通过乘以形状给出的元素数量(注意shape =()意味着有1个元素)通过dtype.itemsize来计算出字节数。

Format Specification: Version 2.0

版本1.0格式只允许数组头的总大小为65535字节。这可以被具有大量列的结构化数据组超过。版本2.0格式将标题大小扩展为4 GiB。numpy.save将自动保存为2.0格式,如果数据需要它,否则它将始终使用更兼容的1.0格式。

因此,报头的第四元素的描述变成:

接下来的4个字节形成一个little-endian unsigned int:头数据HEADER_LEN的长度。

Conventions

对于遵循此格式的文件,我们建议使用“.npy”扩展名。这绝不是一个要求;应用程序可能希望使用此文件格式,但使用特定于应用程序的扩展。在没有明显的替代方案,但是,我们建议使用“.npy”。

为了将多个数组组合成一个文件的简单方法,可以使用ZipFile包含多个“.npy”文件。我们建议对这些归档使用文件扩展名“.npz”。

Alternatives

作者认为,这个系统(或沿着这些线)是关于满足所有要求的最简单的系统。然而,人们必须总是向世界介绍一种新的二进制格式。

HDF5 [2]是一种非常灵活的格式,应该能够以某种方式表示NumPy的所有数组。它可能是唯一广泛使用的格式,可以忠实地表示NumPy的所有数组功能。它已经被科学界普遍接受,特别是NumPy社区。它是存在或不存在NumPy的各种数组存储问题的出色解决方案。

HDF5是一种复杂的格式,或多或少地实现层级文件系统文件。这个事实使得满足一些要求变得困难。对于作者的知识,在写这篇文章时,没有应用程序或库读取或写入甚至不使用规范libhdf5实现的HDF5文件的子集。这个实现是一个大型库,并不总是很容易构建。将它包括在numpy中将是不可行的。

将HDF5的极为有限的子集作为目标可能是可行的。也就是说,它只有一个对象:数组。对数据使用连续存储,应该能够实现足够的格式以提供与所提出的格式相同的元数据。人们仍然可以满足所有的技术要求,如mmapability。

通过生成可以被其他HDF5软件读取的文件,我们将获得巨大的收益。此外,通过提供HDF5的第一个非libhdf5实现,我们将能够鼓励在以前由于库的大小而不可行的应用中更多地采用简单的HDF5。基本工作可能鼓励在其他语言中类似的死简单实现,并进一步扩大社区。

其余的关注是关于格式的逆向可工程性。即使是HDF5的简单子集也将非常难以逆向工程只给一个文件本身。然而,鉴于HDF5的重要性,这可能不是一个实质性问题。

最后,我们将继续本文档中所述的设计。如果有人写代码来处理对我们有用的HDF5的简单子集,我们可能会考虑文件格式的修订。

Implementation

1.0版本的实现首先包含在numpy的1.0.5版本中,并且仍然可用。2.0版本的实现首先包含在numpy的1.9.0版本中。

具体来说,此目录中的format.py文件实现了此处所述的格式。