gzip.open().read() 的尺寸参数

4

在Python中使用gzip库时,我经常会遇到使用.read()函数的代码模式,看起来像这样:

with gzip.open(filename) as bytestream:
    bytestream.read(16) 
    buf = bytestream.read(
        IMAGE_SIZE * IMAGE_SIZE * num_images * NUM_CHANNELS
    )
    data = np.frombuffer(buf, dtype=np.uint8).astype(np.float32)

虽然我熟悉上下文管理器设计模式,但我很难真正理解 with 上下文管理器中第一行代码在做什么。

这是 read() 函数的文档:

从流中最多读取 n 个字符。

从底层缓冲区读取,直到我们有 n 个字符或遇到 EOF。 如果 n 是负数或省略,则读取直到 EOF。

如果是这样,那么第一行代码 bytestream.read(16) 的功能作用应该是读取并跳过前面的 16 个字符,这些字符可能充当元数据或标头。然而,当我有一些图像时,我怎么知道要在 read 调用中使用参数 16,而不是例如 32864 呢?

我记得很多次看到完全相同的代码,除了作者使用 bytestream.read(8) 而不是 bytestream.read(16),或者同样有可能使用任何其他值。逐个字符地挖掘文件没有明显的模式可以确定标头字符的长度。

换句话说,如何确定 read 函数调用使用的参数? 或者如何知道 gzip 压缩文件中标头字符的长度?

我猜想它与字节有关,但在搜索文档和在线参考资料后,我无法确认。

可重现的细节

经过无数小时的疯狂排错,我的假设是前面的 16 个字符表示某种标题或元数据。因此,该代码中的第一行是为了跳过这 16 个字符并将其余部分存储在名为 buf 的变量中。然而,在挖掘数据时,我发现没有办法确定选择值为 16 的原因或方式。我逐个字符读取了字节,并尝试将它们读取并转换为 np.float,但没有明显的模式表明元数据在第 16 个字符结束,实际数据从第 17 个字符开始。

以下代码从此网站读取数据并提取前 30 个字符。请注意,标头行的“结尾”(显然是第 16 个 \x1c` 出现后)和数据从哪里开始不可辨认:

import gzip
import numpy as np

train_data_filename = 'data_input/train-images-idx3-ubyte.gz'
IMAGE_SIZE = 28
NUM_CHANNELS = 1

def extract_data(filename, num_images):
    with gzip.open(filename) as bytestream:
        first30 = bytestream.read(30)
        return first30

first30= extract_data(train_data_filename, 10)
print(first30)
# returns: b'\x00\x00\x08\x03\x00\x00\xea`\x00\x00\x00\x1c\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

如果我们修改代码将它们转换为np.float32,使得所有字符现在都是数字(浮点数),那么似乎没有明显的模式可以区分头部/元数据结束和数据开始的位置。

任何参考或建议都将不胜感激!


它只是指定缓冲区大小。请参见:https://dev59.com/LXNA5IYBdhLWcg3wQ7Yw - I_Al-thamary
这非常特定于个人数据格式。编写代码的人们可能已经了解了他们正在解析的内容,从而做出了所需的假设。你的问题根本没有指定代码打算解析的数据格式,而代码的需求完全由该格式的规范驱动,那么...这个问题如何能够被回答呢? - Charles Duffy
只有非常少量的格式(例如JSON、s-expressions或msgpack)带有模式,而在绝大多数情况下,字段存在于哪些偏移量和其他详细信息都是超出带宽的。如果您很幸运,可能会有一个正式的规范文档,或者类似于protobuf规范的东西,可以用于自动生成机器解析器...但是这些东西是否存在,除非您找到描述其格式的数据作者的文档,否则我们甚至无法告诉您从何处开始。 - Charles Duffy
我明白数值8的选择是特定于手头的问题的。但是问题源于一个可观察到的一般模式,因此我真的希望能够更一般地理解:在给定gzip压缩文件的情况下,如果结束用户不具备文件作者所拥有的先验知识,如何逃避头部/元数据的前n个字符并从文件中读取。 - onlyphantom
为什么你希望在从gzip输入解压缩的流中决定头部大小和在任何其他格式中决定头部大小之间存在任何区别?这不是一个gzip头,它是内容本身内的头部,并且受制于该内容的文件格式。使用gzip.open(...).read()只会返回(从gzip的角度来看)数据内容 - 根本没有返回任何gzip特定的元数据。 - Charles Duffy
显示剩余2条评论
2个回答

2
从gzip的角度来看,它返回给您的所有内容都是数据。没有元数据或gzip特定的头部内容预置到该数据流中,因此不需要任何算法来确定gzip添加了多少内容到该流中:它添加的字节数为零。
向下滚动到您链接的页面底部,有一个标题为MNIST数据库文件格式的标头。
该格式规范告诉您确切的格式以及每个标头使用了多少字节。具体来说,每个文件的前四个项描述如下:
0000     32 bit integer  0x00000803(2051) magic number 
0004     32 bit integer  60000            number of images 
0008     32 bit integer  28               number of rows 
0012     32 bit integer  28               number of columns 

因此,如果您想跳过这四个项目,您需要从顶部减去16字节。

非常感谢,这将在很大程度上有助于澄清一些问题。您能详细说明一下如何计算“从顶部减去16字节”吗?非常感谢。 - onlyphantom
1
所列项目为4个32位整数;8位为1字节;因此(4 * 32/8)== 16。 - Charles Duffy
很完美。谢谢你。 - onlyphantom
1
文件格式看起来比那要复杂一些。魔数编码了数据类型和数据维度的数量。第三个字节是 0x08,网页列出其表示数据是无符号字节。第四个字节是 0x03,表示将有三个之后的维度,每个维度都是32位大端整数(我假设是无符号的,但没有说明)。所以总共将有一个16字节的头部。代码片段似乎基本忽略了一般的文件格式,专注于特定的文件格式子集。 - Dunes
@Dunes,如果我理解正确的话,前四个字节是你在前两个句子中解释的内容。然后再加上12个额外的字节,因为我们认为0x03表示三个后续的维度,每个维度4个字节,总共12个字节。12+4(前4个)= 16。这样对吗? - onlyphantom

0
从代码片段中,bytestream.read(16) 读取或跳过bytestream的前16个字节。当你引用read()最多从流中读取n个字符时,它确实是这样做的,但似乎Python将单个字符存储在1个字节中,使得16个字符占据16个字节。
了解有关字符和字节的更多信息 https://pymotw.com/3/gzip/#reading-compressed-data 代码段主要关注buf的内容,跳过流的前16个字节。为了了解如何确定传递给第一个bytestream.read()的参数,即决定要跳过压缩图像文件的字节数,我们必须了解代码的其余部分是做什么的。特别是,我们正在读取哪个文件,并且使用numpy(?)库完成什么任务(在1D numpy数组中保存RGB映像?)。
我绝对不是图像处理方面的专家,但似乎bytestream.read(16)是一种针对某些独特压缩图像文件处理问题的独特解决方案。因此,在没有看到更多代码并理解片段背后更多逻辑的情况下,很难确定要跳过多少字节。

根据我在网上阅读的文档,代码 bytestream.read(16) 读取并跳过前16个字符。很可能它试图跳过元数据,例如标题。然而,如何确定该参数为16而不是8或32仍然超出了我的能力范围。让我通过添加更多细节来更新问题。 - onlyphantom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接