为啥 h5py 在向数据集添加 3 个可变长度字符串时会抛出错误?

Posted

技术标签:

【中文标题】为啥 h5py 在向数据集添加 3 个可变长度字符串时会抛出错误?【英文标题】:Why does h5py throw an error when adding 3 variable length strings to a dataset?为什么 h5py 在向数据集添加 3 个可变长度字符串时会抛出错误? 【发布时间】:2021-09-26 12:36:45 【问题描述】:

我正在尝试使用 h5py (Python 3) 设置并写入 HDF5 数据集,其中包含复合对象的一维数组。每个复合对象由三个可变长度的字符串属性组成。

     with h5py.File("myfile.hdf5", "a") as file:
         dt = np.dtype([
             ("label", h5py.string_dtype(encoding='utf-8')),
             ("name", h5py.string_dtype(encoding='utf-8')),
             ("id", h5py.string_dtype(encoding='utf-8'))])
         dset = file.require_dataset("initial_data", (50000,), dtype=dt)
         dset[0, "label"] = "foo"

当我运行上面的示例时,最后一行代码导致 h5py(或更准确地说是 numpy)抛出一个错误:

“无法更改对象数组的数据类型。”

我是否正确理解"foo" 的类型不是h5py.string_dtype(encoding='utf-8')

怎么会?我该如何解决这个问题?

更新 1: 进入堆栈跟踪,我可以看到错误是从名为 _view_is_safe(oldtype, newtype) 的内部 numpy 函数引发的。在我的情况下,oldtypedtype('O'),但 newtypedtype([('label', 'O')]),这会导致引发错误。

更新 2: 我的问题已在下面成功回答,但为了完整起见,我将链接到可能相关的 GH 问题:https://github.com/h5py/h5py/issues/1921

【问题讨论】:

"foo" 是一个 Python 字符串(unicode)。所以它需要进行一些转换,这大概就是 traceback 的错误告诉我们的。将字符串写入dataset 是一个不断发展的功能,所以我不熟悉当前的细节。我必须找到文档,并根据他们的示例来处理您的案例。 【参考方案1】:

您将dtype 设置为可变长度字符串的元组,因此您可以一次设置所有元组。通过只设置标签元素,其他两个元组值没有被设置,所以它们不是字符串类型。

示例:

import h5py
import numpy as np

with h5py.File("myfile.hdf5", "a") as file:
    dt = np.dtype([
        ("label", h5py.string_dtype(encoding='utf-8')),
        ("name", h5py.string_dtype(encoding='utf-8')),
        ("id", h5py.string_dtype(encoding='utf-8'))])
    dset = file.require_dataset("initial_data", (50000,), dtype=dt)

#Add a row of data with a tuple:
    dset[0] = "foo", "bar", "baz"
 
#Add another row of data with a np recarray (1 row):
    npdt = np.dtype([
        ("label", 'S4'),
        ("name", 'S4'),
        ("id", 'S4') ])
    dset[1] = np.array( ("foo1", "bar1", "baz1"), dtype=npdt )
       
#Add 3 rows of data with a np recarray (3 rows built from a list of arrays):
    s1 = np.array( ("A", "B", "C"), dtype='S4' )
    s2 = np.array( ("a", "b", "c"), dtype='S4' )
    s3 = np.array( ("X", "Y", "Z"), dtype='S4' )
    recarr = np.rec.fromarrays([s1, s2, s3], dtype=npdt)
    dset[2:5] = recarr

结果 #1:

使用所有 3 种方法的结果:

【讨论】:

@urig 我自己对此很感兴趣,所以我会调查一下。也就是说,在这种情况下,None 是一个有效类型,因此您绝对可以使用类似dset[0] = "foo", None, "baz" 的东西来部分填充集合。基本思想是你告诉h5py 期望一个三元素元组作为输入,除了三元素元组之外的任何东西都会破坏dtype 设置的规则。 @urig, @Abstract,您还可以使用 numpy recarrays(有多种创建方法)添加数据。 “最佳方法”实际上取决于您的起始数据结构。换句话说,选择最简单的成功编码路径。 :-) 我没有添加新答案,而是扩展了 Abstract 的示例以显示另外 2 个方法。让我知道您是否更喜欢它的新答案。我还删除了重复的 h5pyFile() 条目。 注意:这个过程是“复杂的”,因为你有可变长度字符串的复合数据。当您拥有具有“典型” Python/Numpy 类型(整数、浮点数、固定长度字符串)的复合数据时,它会更简单。 关于您的问题,我倾向于根据您的数据(复合对象的 1d 数组,每个具有 3 个可变长度字符串)说“是”。 但是,一次加载 1 行数据是最慢的方法。 50_000 行的性能可能是可以接受的。查看这个答案,该答案显示 I/O 性能随着写入数据块的大小变小(并且写入调用次数增加)而降低。 [Pytables 写入速度比 h5py 快](***.com/a/57963340/10462884 对不起,造成混乱。我不是建议你使用 PyTables。那篇文章是关于 PyTables 与 h5py 性能的问题。我怀疑根本原因是写入块大小(用户一次写入 64 行)。所以我研究了速度与写入块性能,并创建了图表。我不知道 PyTables 是否有可变长度的字符串。我不在我的代码中使用它们。我只在回答 SO 问题时与他们合作。 :-)

以上是关于为啥 h5py 在向数据集添加 3 个可变长度字符串时会抛出错误?的主要内容,如果未能解决你的问题,请参考以下文章

H5py 存储字符串列表列表

H5PY键读取缓慢

Python h5py - 为啥我会收到广播错误?

H5PY 键读取速度慢

从 Python 将字符串列表存储到 HDF5 数据集

使用 h5py 沿新轴将数据添加到现有 h5py 文件