7.4 变长字符串
你若是用过一段时间的NumPy,就会习惯它的设计中一个细微但重要的地方:其数组中所有的元素具有相同的大小。这个设计有很多好处。比如,为了定位一个4字节浮点数据集的第115个元素,你知道自己只需要从数组头部往前找460字节。你每天用于计算的大多数类型都有固定的大小——比如你一旦选定了使用双精度浮点,那它们都是8字节。
然而字符串类型打破了这个规则。我们之前见过,NumPy本地支持两种字符串类型:8位“ASCII”字符串类型和32位“Unicode”字符串类型。你必须在创建字符串类型时显式指定字符串长度。让我们创建一个长度为3的ASCII。字符串数组并将其初始化:
限制很明显:长度超过3个字符的元素都会被截断,信息会丢失。简单增加字符串类型的长度看上去很诱人,比如加到100或256。但是这样会导致我们浪费大量内存,而且依然无法保证我们的猜测足够大:
虽然并不是所有的应用程序都会遇到这个问题,但一个无法回避的事实是:真实世界的字符串理论上可以具有任意长度。
幸运的是,HDF5有一种机制可以处理这个问题:变长字符串。和Python本地字符串(以及C字符串)一样,只要内存里放得下,变长字符串可以具有任意长度。下面介绍如何使用它们。
7.4.1 变长字符串的数据类型
首先,NumPy完全不支持变长字符串,所以我们要使用h5py提供的一种特殊dtype:
看上去简直就是一团糟。但其实它是一个NumPy标准的dtype,附加有某些元数据,其底层dtype类型是object:
O类型的NumPy数组存放了普通的Python对象。所以这个dtype实际上是在说:“这是一个对象数组,用于存放Python字符串。”
提示
根据你h5py的版本,你在打印dtype时可能看到不同的结果,这是因为不同版本的h5py对特殊数据的附加方式不同,不要依赖任何一种实现。你应该总是使用special_dtype函数而不是自己尝试去拼凑一个。
7.4.2 变长字符串数据集的使用
你可以用这个“特殊”的dtype创建数组。比如,创建一个具有100个变长字符串的数据集:
你可以把任何看上去像字符串的东西写入该数据集,包括普通的Python字符串和NumPy定长字符串:
当你读取一个元素时会得到一个Python字符串:
当你读取多个元素时会得到一个Python字符串的对象数组:
这里要给你一个警告:由于技术原因,返回数组的dtype是“object”而不是我们用h5py.special_dtype创建的那个特殊dtype:
这是非常少见的dset[…].dtype != dset.dtype的情况之一。
7.4.3 字节字符串和Unicode字符串
虽然上面的例子和本书其余地方一样,假设你使用的是Python 2,但你要注意无论是Python 2还是Python 3都存在两种字符串。它们在文件中的存储有细微的差别,在国际化和数据移植性上有不同的表现。
对字节字符串和Unicode字符串的详细讨论超出了本书的范畴。本书的重点是讨论两种类型和HDF5的互动。
上例用到的Python 2的“str”类型在Python世界里通常被称为字节字符串。顾名思义,它们是单个字节元素的序列。它们在Python 2和Python 3中的名字都是bytes(在Python 2中,它是str的别名,而在Python 3中则是一个完全独立的类型)。它们被用于存放严格的二进制字符串,在Python 2中它们还有第二个目的:代表ASCII或Latin-1编码的文本。
在HDF5的世界里,它们代表“ASCII”字符串。它们被期望包含0~127的值以代表ASCII文本,不过HDF5对此没有任何校验。在Python 2,你可以用下面的dtype创建一个支持ASCII字符集的数据集:
或者更可读一点:
由于HDF5的很多第三方应用只能识别ASCII字符串,所以这是最具兼容性的配置。用这样的dtype创建出的数据集使用ASCII字符集。
7.4.4 使用Unicode字符串
和刚刚讨论的str/bytes“字节”字符串不同,Python 2的Unicode类型代表“文本”字符串。在Python 3,字节字符串是bytes,而“文本”字符串则是——看仔细了——str。这真是极好的。
这些字符串内含更抽象的Unicode字符。你不需要担心它们是如何被表达的。在保存它们之前,你需要显式对其编码,这意味着将它们翻译成字节序列。将“文本”字符串翻译成字节字符串的规则被称为编码。HDF5使用UTF-8编码,这是一种非常节省字符串空间的编码方式,主要包含西方字符。
你可以在HDF5中直接存储这些“Unicode”字符串或称“文本”字符串,和之前类似,只需要使用一种“特殊”dtype:
和之前一样,你可以用这种dtype创建支持Unicode的数据集:
当你创建这种数据集时,底层的HDF5字符集被设置为“UTF-8”。唯一的缺点是某些老版本的第三方应用,如IDL,可能无法读取你的字符串。如果你的程序需要兼容那些遗留代码,千万要测试!
警告
还记得吗?Python 3默认的字符串str实际上是Unicode字符串,所以在Python 3使用h5py.special_dtype(vlen=str)会让你创建一个UTF-8数据集而不是兼容一切的ASCII数据集。如果你需要ASCII数据集,你应该总是使用vlen=bytes。
7.4.5 不要在字符串中保存二进制数据
请注意HDF5允许你用special_dtype(vlen=bytes)创建的“ASCII”数据集保存原始二进制数据。虽然在理论上可以这么做,但其实有很大的风险。考虑到字符串内部的处理细节,如果你的二进制字符串中包含NULL(“\x00”),它会被默不作声地截断!
保存原始二进制数据最好的方式是使用“不透明”类型(90页,不透明类型)。
7.4.6 确保你Python 2程序的未来
最后,这里有一些简单的规则确保你自己不被bytes/unicode问题逼疯。当你使用Python自带的翻译工具2to3将程序移植到Python 3上时,这些规则也会对你有帮助。
1.时刻谨记文本和字节的区别,在代码中清楚分别两者。
2.对于字节字符串,始终使用bytes类型而不是str。对于字面形式,你甚至可以使用“b”前缀,比如:b“Hello”。用special_dtype创建字节字符串时,始终使用bytes。
3.对于文本字符串则应该使用str类型,或者更好的选择是用unicode类型。Unicode字面形式以“u”为前缀:u“Hello”。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论