PyTables 简介

PyTables 简介#

PyTables 构建在 HDF5 库之上,使用 Python 语言和 NumPy 包。它具有面向对象的接口,结合了为代码性能关键部分(使用 Cython 生成)的 C 扩展,使其成为快速且极其易于使用的工具,用于交互式浏览、处理和搜索大量数据。PyTables 的重要特性是它优化了内存和磁盘资源,使得数据占用的空间比其他解决方案(特别是如果使用 on-flight 中压缩)要小得多,例如关系对象数据库。

PyTables 的目标是让最终用户能够轻松地操作分层结构中的数据表和数组对象。其底层分层数据组织的基石是优秀的 HDF5 库(参见 HDGF1)。

需要注意的是,这个包并不是为了作为 HDF5 API 的完整封装,而是为了提供灵活、非常 Pythonic 的工具,用于处理(任意)大量数据(通常大于可用内存),这些数据以分层和持久磁盘存储结构组织在表和数组中。

表被定义为记录的集合,其值存储在固定长度的字段中。所有记录具有相同的结构,每个字段中的所有值具有相同的数据类型。对于像 Python 这样的解释型语言来说,固定长度和严格的数据类型可能看起来是奇怪的要求,但如果目标是高效地保存大量数据(例如由许多数据采集系统、互联网服务或科学应用程序生成的数据),这些要求是有用的,可以减少对 CPU 时间和 I/O 的需求。

为了在 Python 中模拟映射到 HDF5 C 结构体的记录,PyTables 实现了特殊类,以便轻松定义其所有字段和其他属性。PyTables 还提供了强大的接口来挖掘表中的数据。表中的记录在 HDF5 命名方案中也被称为复合数据类型。

例如,你可以通过简单地声明带有命名字段和类型信息的类来在 Python 中定义任意表,如下例所示:

class Particle(IsDescription):
    name      = StringCol(16)   # 16-character String
    idnumber  = Int64Col()      # signed 64-bit integer
    ADCcount  = UInt16Col()     # unsigned short integer
    TDCcount  = UInt8Col()      # unsigned byte
    grid_i    = Int32Col()      # integer
    grid_j    = Int32Col()      # integer

    # A sub-structure (nested data-type)
    class Properties(IsDescription):
        pressure = Float32Col(shape=(2,3)) # 2-D float array (single-precision)
        energy   = Float64Col(shape=(2,3,4)) # 3-D float array (double-precision)

接下来,您将这个类传递给表格构造器,用您的值填充其行,并将(任意大的)集合保存到文件中以实现持久化存储。之后,这些数据可以通过 PyTables 或甚至其他 HDF5 应用程序(如 C、Fortran、Java 或其他提供与 HDF5 接口的库的语言)轻松检索和后处理。

在 PyTables 中,另一个重要的实体是数组对象,它们与表格类似,不同之处在于它们的所有组件都是同质的。它们有不同的类型,比如通用型(为数值数组提供了一种快速简便的处理方式)、可扩展型(数组可以沿一个维度扩展)和变长型(数组中的每行可以有不同数量的元素)。

主要特性#

PyTables 利用 Python 提供的面向对象和内省能力、HDF5 强大的数据管理功能、NumPy 的灵活性以及 Numexpr 对网格状组织的大量对象的高性能操作,提供了以下特性:

  • 支持表实体:您可以通过添加或删除记录来定制数据表。支持大量行(最多 \(2^{63}\),远超内存容量)。

  • 多维和嵌套表单元:您可以声明一个列由具有任意维度的值组成,而不仅仅是标量,这是大多数关系数据库唯一允许的维度。您甚至可以声明由其他列(不同类型)组成的列。

  • 表列的索引支持:如果您有大型表并希望快速查找满足某些条件的列中的值,这将非常有用。

  • 支持数值数组NumPy 数组可以用作表的有用补充,用于存储同质数据。

  • 可扩展数组:您可以在现有数组的任何维度上添加新元素(但只能在一个维度上)。此外,您可以通过使用强大的扩展切片机制,无需将整个数据集加载到内存中,即可访问数据集的切片。

  • 可变长度数组:这些数组中的元素数量可以逐行变化。这在处理复杂数据时提供了很大的灵活性。

  • 支持分层数据模型:允许用户清晰地组织所有数据。PyTables 在内存中构建了一个对象树,复制了底层文件的数据结构。通过遍历和操作这个对象树,可以访问文件中的对象。此外,为了提高效率,这个对象树是以懒惰的方式构建的。

  • 用户定义的元数据:除了支持系统元数据(如表的行数、形状、风味等),用户还可以指定任意元数据(例如,房间温度或收集的 IP 流量协议),以补充实际数据的含义。

  • 读取/修改通用 HDF5 文件的能力:PyTables 可以访问通用 HDF5 文件中的广泛对象,如复合类型数据集(可以映射到 Table 对象)、同质数据集(可以映射到 Array 对象)或可变长度记录数据集(可以映射到 VLArray 对象)。此外,如果数据集不受支持,它将被映射到一个特殊的 UnImplemented 类(参见 The UnImplemented class),这将允许用户看到数据在那里,尽管它将无法访问(但您仍然可以访问数据集的属性和一些元数据)。通过这种方式,PyTables 可能可以访问和修改大多数 HDF5 文件。

  • 数据压缩:支持开箱即用的数据压缩(使用 Zlib、LZO、bzip2 和 Blosc 压缩库)。当您有重复的数据模式并且不想花费时间寻找优化的存储方式时,这很重要(节省了分析数据组织所花费的时间)。

  • 高性能 I/O:在现代系统上存储大量数据时,表和数组对象的读写速度仅受底层 I/O 子系统性能的限制。此外,如果您的数据是可压缩的,甚至这个限制也是可以克服的!

  • 支持大于 2 GB 的文件:PyTables 自动继承了底层 HDF5 库的这一能力(假设您的平台支持 C long long 整数,或在 Windows 上支持 __int64)。

  • 架构无关:PyTables 经过精心编码(与 HDF5 本身一样),考虑了小端/大端字节顺序问题。因此,您可以在大端机器(如 Sparc 或 MIPS)上写入文件,并在其他小端机器(如 Intel 或 Alpha)上读取它,而不会出现问题。此外,它已成功使用 64 位平台(Intel-64、AMD-64、PowerPC-G5、MIPS、UltraSparc)上的代码进行了测试,这些代码由 64 位感知编译器生成。

对象树#

底层 HDF5 库的分层模型允许 PyTables 以树状结构管理表和数组。为了实现这一点,动态创建了一个对象树实体,模仿磁盘上的 HDF5 结构。通过遍历这个对象树来读取 HDF5 对象。通过检查元数据节点,您可以很好地了解对象中保存了什么样的数据。

对象树中的不同节点是 PyTables 类的实例。有几种不同类型的类,但最重要的类是 Node、Group 和 Leaf 类。PyTables 树中的所有节点都是 Node 类的实例。Group 和 Leaf 类是 Node 的后代。Group 实例(以下简称组)是一种包含零个或多个组或叶实例的结构,以及补充元数据。Leaf 实例(以下简称叶)是实际数据的容器,不能包含更多的组或叶。Table、Array、CArray、EArray、VLArray 和 UnImplemented 类是 Leaf 的后代,并继承其所有属性。

在许多方面,使用组和叶与在 Unix 文件系统上使用目录和文件类似,即一个节点(文件或目录)总是一个且仅有一个组的子节点(目录),即其父组 1。在该组内,通过其名称访问节点。与 Unix 目录和文件的情况一样,对象树中的对象通常通过给出其完整(绝对)路径名称来引用。在 PyTables 中,这个完整路径可以指定为字符串(例如 ‘/subgroup2/table3’,使用 / 作为父/子分隔符),或以自然名称模式编写的完整对象路径(例如 file.root.subgroup2.table3)。

自然命名的支持是 PyTables 的关键方面。这意味着节点对象的实例变量名称与其子节点的名称相同 2。这在许多情况下非常 Pythonic 且直观。请参阅教程 Reading(和选择)表中的数据以获取使用示例。

您还应该意识到,文件中的所有数据并未加载到对象树中。元数据(即描述实际数据结构的特殊数据)仅在用户想要访问时加载(稍后详述)。此外,实际数据在用户请求之前不会读取(通过在特定节点上调用方法)。使用对象树(元数据),您可以检索有关磁盘上对象的信息,例如表名、标题、列名、列中的数据类型、行数,或者在数组的情况下,形状、类型代码等。您还可以在树中搜索特定类型的数据,然后读取并处理它。在某种意义上,您可以将 PyTables 视为一种工具,它将 Python 对象的相同内省能力应用于持久存储中的大量数据。

值得注意的是,PyTables 具有一个元数据缓存系统,该系统懒惰地加载节点(即按需加载),并卸载一段时间未使用的节点(遵循最近最少使用模式)。需要强调的是,节点在解除引用后(在 Python 引用计数的意义上)进入缓存,并且它们可以直接从缓存中恢复(通过再次引用它们),而无需执行从磁盘反序列化的过程。此功能允许快速处理具有大型层次结构的文件,并具有低内存消耗,同时保留了以前实现的对象树的所有强大浏览功能。有关此新元数据缓存系统带来的优势的更多事实,请参见 OPTIM

为了更深入地理解这个对象树实体的动态特性,从一段示例 PyTables 脚本(你可以在 examples/objecttree.py 找到)开始,该脚本用于创建 HDF5 文件:

import tables as tb

class Particle(tb.IsDescription):
    identity = tb.StringCol(itemsize=22, dflt=" ", pos=0)  # character String
    idnumber = tb.Int16Col(dflt=1, pos = 1)  # short integer
    speed    = tb.Float32Col(dflt=1, pos = 2)  # single-precision

# Open a file in "w"rite mode
fileh = tb.open_file("objecttree.h5", mode = "w")

# Get the HDF5 root group
root = fileh.root

# Create the groups
group1 = fileh.create_group(root, "group1")
group2 = fileh.create_group(root, "group2")

# Now, create an array in root group
array1 = fileh.create_array(root, "array1", ["string", "array"], "String array")

# Create 2 new tables in group1
table1 = fileh.create_table(group1, "table1", Particle)
table2 = fileh.create_table("/group2", "table2", Particle)

# Create the last table in group2
array2 = fileh.create_array("/group1", "array2", [1,2,3,4])

# Now, fill the tables
for table in (table1, table2):
    # Get the record object associated with the table:
    row = table.row

    # Fill the table with 10 records
    for i in range(10):
        # First, assign the values to the Particle record
        row['identity']  = f'This is particle: {i:2d}'
        row['idnumber'] = i
        row['speed']  = i * 2.

        # This injects the Record values
        row.append()

    # Flush the table buffers
    table.flush()

# Finally, close the file (this also will flush all the remaining buffers!)
fileh.close()

这个小程序创建了名为 objecttree.h5 的简单 HDF5 文件。当文件创建时,对象树中的元数据在内存中更新,而实际数据保存到磁盘。当您关闭文件时,对象树不再可用。然而,当您重新打开此文件时,对象树将从磁盘上的元数据在内存中重建(这是以懒惰的方式进行的,以便仅加载用户所需的对象),允许您以与最初创建时完全相同的方式使用它。

安装#

pip install tables

或者

conda install pytables