5.3 使用链接
在文件里赋予对象一个名字意味着什么?根据上面的例子,你可能会认为这个名字是该对象的一部分,就像数据集的类型或形状一样。
事实并非如此。在组对象和其成员对象之间还有一层链接的概念。
5.3.1 硬链接
HDF5对链接的处理和现代操作系统一样。数据集和组等对象本身并没有名字,只有一个文件内的地址(字节偏移量)用于查找。当你赋予对象一个名字,该地址被记录在组内并关联了你提供的这个名字,形成了一个链接。
这意味着HDF5文件内的对象可以拥有多个名字。事实上有多少个指向它们的链接就有多少个名字。指向对象的链接数量会被记录下来,当该数量变为0时,该对象占用的空间会被释放。
这是HDF5默认的链接类型。为了跟本章后续介绍的其他链接类型有所区分,这种链接被称为硬链接。
下面是一个重名行为的例子。我们会创建一个简单文件,它包含一个组,并在/x上创建一个硬链接指向它:
现在我们创建第二个链接指向该组。你可以用Python标准字典风格的赋值语句:
当我们从位置/y获取对象,我们会得到同一个组:
你可能会想,既然文件中的对象没有一个唯一的名字,那么.name属性会返回什么呢?让我们实验一下:
HDF5会尽最大努力将获取该对象时使用的名字返回,但无法保证一定能做到这一点。只要该对象有名字,那么当你访问.name时就能得到一个名字,但它可能不是你期望的。
什么叫“只要该对象有名字”?在HDF5中创建一个无名对象是完全合法的,只需要指定None:
上例的grpz组在文件中存在,但没法从根组获取。如果我们删除了Python对象grpz,该组就会被删除,文件内的空间也会被释放。为了避免这一点,我们可以在之后将该组链接上一个文件结构:
重名问题还影响了.parent属性的行为。为了令行为保持一致,在h5py中,obj.parent会根据obj.name返回“parent”对象。比如说,如果obj.name是/foo/bar,那么obj.parent.name就是/foo。
我们可以用Python内建的posixpath模块来模拟这种关系:
为了删除链接,我们需要使用字典风格的del group[name]:
一旦一个对象所有的硬链接都被删除(且该对象未在Python中打开),则该对象被销毁:
5.3.2 剩余空间和重新打包
当一个对象(比如一个大数据集)被删除,它所占用的磁盘空间会被新的对象重新使用。但是在文件被关闭之后,HDF5并不记录这种“剩余空间”。如果你在文件关闭时还没有用到这些空间,那么你的文件中就会留下一个无法被重新使用的“空洞”。
这个问题现在在HDF组织处于较高的开发优先级。在该问题被解决之前,如果你发现你的文件大得异乎寻常,你可以用h5repack工具对其“重新打包”:
5.3.3 软链接
“硬”链接是将一个链接名字和文件中的对象关联到一起,与之不同的“软”链接则是在对象内保存了指向一个对象的路径。Linux或Mac OS X用户应该会比较熟悉这种链接方式。
让我们来看下面这个例子。我们会创建一个文件,在文件内创建一个组,在组内创建一个数据集:
如果我们在根组内创建一个数据集的硬链接,该链接将始终指向那个数据集,无论该数据集被删除还是重命名:
让我们把数据集名字改回来,然后创建一个软链接指向/mygroup/dataset路径。为了告诉HDF5我们要创建软链接,需要在文件中创建一个h5py.SoftLink类的实例:
软链接对象十分简单。它们只有一个.path属性保存了它们被创建时指定的地址:
记住,h5py.SoftLink实例仅是为了提供Python使用上的方便,HDF5内不存在相对应的实体。如果你不把文件中的一个名字赋给它,任何事情都不会发生。
回到我们的例子,因为只保存了路径,当我们重命名数据集并用别的东西取代它的位置时,/softlink就会指向新的对象:
所以说软链接其实就是指向了一个“位于某个特定路径的对象”,而不是文件内某个确定的对象。在某些情况下它会非常有用。比如,当你需要改变某个路径固定的数据集而又不想破坏文件其他地方指向它的链接时。
软链接的值在创建时不会被检查。如果你指定了一个非法路径(或对象后来被重命名或删除),那么当你去访问时会失败并抛出异常。根据HDF5报告此类错误的方式,你会得到一个KeyError异常,就像访问一个不存在于组内的名字一样:
另外,由于软链接只记录路径,它们不会参与硬链接的引用计数。所以如果你有一个软链接/softlink指向一个硬链接为/a的对象,在你删除硬链接(del f[“/a”])时,该对象会被销毁,软链接会失效。
提示
你可能会想知道一个失效的软链接出现在items()或values()会发生什么。答案是:它会被一个None对象替代。
5.3.4 外部链接
从HDF5 1.8开始又增加了一个新的链接类型。相比于文件内部的硬链接和软链接,外部链接允许你指向别的文件内的对象。它因为是透明的,所以成为了HDF5最酷的功能之一,同时也是最难以追踪的问题之一。
一个外部链接有两个部分:文件名,以及该文件内某个对象的(绝对)名字。和软链接一样,你需要先创建一个“标记”对象,这次是叫h5py.ExternalLink。
我们先创建一个仅包含一个对象的文件,然后从另一个文件链接过去:
外部链接和软链接一样是透明的。只要不失效,我们就能得到它指向的那个组或数据集而不是某个中间对象。所以当我们访问刚刚创建的链接时会得到一 个组:
但是经过仔细观察,我们会发现该对象位于另一个文件内:
记住,这会导致看上去有点奇怪的结果。比如,当你在获得的对象上使用.parent属性时,它指向那个外部文件的根组,而不是链接所在的文件:
外部链接创建时会检查文件和对象的名字。如果HDF5找不到文件,或在文件中找不到指定的对象,会抛出一个异常:
外部链接的两个主要危害是:(1)链接指向的文件在你访问时可能不存在;(2)你轻易就会从一个文件跑去另一个文件。
我们对(1)基本上无能为力,你得自己整理文件,记住各自的链接关系。(2)更加危险,特别是当你用Python的迭代和items()等方法遍历访问的组内成员都包含外部链接的时候。如果你不希望你的应用程序跨越文件的边界,一定要记得检查遍历对象的.file属性,确认它位于哪个文件。
最后提一句,h5py目前还不支持设置搜索路径。当HDF5在文件里遇到一个外部链接时,首先会在该文件所在的目录下查找目标文件,找不到则在当前的工作目录下查找,还找不到就到此为止。
5.3.5 对象名字注解
你可能已经意识到,当你在获取对象名字时,得到的是一个Python的Unicode对象:
因为HDF5 1.8(以及Python 3)改进了对Unicode的支持,h5py从2.0版开始使用Unicode对象来表示对象名字。文件内对象的名字总是一个“文本”字符串,这意味着它们是字符的序列。与之相对,“字节”字符串是8位字节的序列,通常用于存储ASCII或Latin-1编码的文本。
这样做的一个好处是文件对象的名字现在支持国际字符了,你不再需要将任何东西转化成ASCII以后才能放入HDF5系统。这些名字的存储采用了HDF老版本1.6可以接受的最兼容的策略。
要使用Unicode名字,你只需要在创建对象或链接时提供一个Unicode字符串:
如果你的字符串是“文本”字符串,h5py会在存储前将其转化成HDF5支持的UTF-8编码。如果你提供的是一个“字节”字符串(就像我们前面大多数例子那样),h5py则会直接使用。技术上来说,你可以用字节字符串的格式来存储非UTF-8的文本字符串,但我们强烈建议你不要这么做。如果你收到一个文件里面包含这种“不合规格”的对象名字,h5py会传回原始的字节字符串让你自己去猜那是什么东西。
5.3.6 用get决定对象类型
我们之前提到过Group对象支持熟悉的字典风格的get方法,并演示了当被访问的组内对象不存在时如何避免抛出KeyError。然而这里还需要提的是,h5py的get其实比Python的get功能更强大。
和默认的字典风格相比我们有两个额外的关键字:getclass和getlink。关键字getclass允许你获取对象的类型而无需打开对象。这在HDF5层面只需要读取一些元数据,所以非常快。
下面的例子里,我们首先创建一个文件,它包含一个组和一个数据集:
我们可以用get获取对象的类型:
第二个关键字getlink会告诉你链接的属性:
你会注意到这里被返回的对象是SoftLink和ExternalLink的实例,里面包含了完整的路径信息。这是从链接中获取此类信息的标准方式。
至于subgroup和dataset中的硬链接,你会看到有一个h5py.HardLink的实例。它纯粹就是为了支持get,自身没有任何方法或属性。
最后,如果你只是想要知道链接的类型,而不需要链接的文件信息和路径信息,你可以结合使用getclass和getlink关键字来获取链接的类:
提示
你可能已经注意这里见到的大多数类都是被定义在h5py._hl子模块,比如上面的h5py._hl.group.SoftLink。这是一个实现上的细节,将来可能会有变化。在使用isinstance检查时,你应该直接使用h5py模块上的类名(如h5py.SoftLink)。
5.3.7 用require简化你的应用程序
和Python字典不同,在h5py里,你不能直接重写组内成员:
对于手动建立的硬链接也是如此:
这个功能的设计是为了阻止数据丢失。因为当对象从组内被删除时会被立即销毁,所以你必须显式删除链接,而不能让HDF5帮你删除:
在现实世界的代码中这会导致一些令人头疼的问题。比如,下面这段分析代码会创建一个文件并将结果写入一个数据集:
每次运行这段代码都会重写整个文件,如果文件中已有很多数据集和组,这可能不太合适。但如果我们不以w模式打开文件,那么我们的程序只会在第一次运行时正常工作,除非我们在每次运行前手动删除输出文件。
为了解决这个问题,create_group和create_dataset有两个对应方法叫require_group和require_dataset。它们和前者做同样的事情,只不过会首先检查组或数据集是否存在,若存在则直接返回该对象。
create版本和require版本具有相同的参数和关键字。对于require_dataset,h5py还会额外检查形状和类型是否和已存在的数据集一致,如果不一致则抛出异常:
这里有一个小细节,就是仅当形状不一致或参数的类型精度高于已存在的数据集类型精度时才会发生冲突。所以如果已存在的数据集类型是int64,而require_dataset要求的精度是int32,那么它会成功:
冲突检查使用了NumPy转换规则。你可以自己用np.can_cast进行类型测试。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论