为啥 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 函数引发的。在我的情况下,oldtype
是 dtype('O')
,但 newtype
是 dtype([('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 个可变长度字符串时会抛出错误?的主要内容,如果未能解决你的问题,请参考以下文章