使用结构体读写头部信息
我有一个文件头,里面包含了一些关于文件内容的信息,比如版本信息和其他字符串值。
写入文件其实不太难,看起来很简单:
outfile.write(struct.pack('<s', "myapp-0.0.1"))
但是,当我尝试在另一个方法中读取文件头时:
header_version = struct.unpack('<s', infile.read(struct.calcsize('s')))
我遇到了以下错误:
struct.error: unpack requires a string argument of length 2
我该如何修复这个错误,具体是什么地方出问题了呢?
1 个回答
写文件其实并不难,看起来很简单:
但其实没有你想的那么简单。试着看看文件里的内容,或者直接打印出你写的东西:
>>> struct.pack('<s', 'myapp-0.0.1')
'm'
正如文档所解释的:
对于格式字符
's'
来说,计数被理解为字符串的大小,而不是像其他格式字符那样的重复计数;例如,'10s'
表示一个10字节的字符串,而'10c'
表示10个字符。如果没有给出计数,默认值是1。
那么,你该怎么处理这个问题呢?
如果不需要,就别用
struct
。使用struct
的主要原因是为了和C代码交互,直接从缓冲区/文件/套接字等读写C的struct
对象,或者是以类似风格编写的二进制格式(比如IP头)。它并不适合用来一般性地序列化Python数据。正如Jon Clements在评论中指出的,如果你只想存储一个字符串,就直接write
这个字符串。如果你想存储更复杂的东西,可以考虑使用json
模块;如果想要更灵活、更强大,可以使用pickle
。使用固定长度的字符串。如果你的文件格式要求名字必须始终小于或等于255个字符,那就写
'<255s'
。短字符串会被填充,长字符串会被截断(你可能想加个检查,以便在截断时抛出异常,而不是默默地截断)。使用某种方式传递长度信息。最常见的是长度前缀。(你可能可以使用
'p'
或'P'
格式来帮助,但这真的取决于你想匹配的C布局/二进制格式;通常你需要做一些比较麻烦的事情,比如struct.pack('<h{}s'.format(len(name)), len(name), name)
。)
至于你的代码为什么失败,有多个原因。首先,read(11)
并不保证能读取11个字符。如果文件里只有1个字符,那你就只能得到这个。其次,你实际上并没有调用read(11)
,而是调用了read(1)
,因为struct.calcsize('s')
返回1
(这个原因应该很明显)。第三,你的代码可能并不是你上面展示的那样,或者infile
的文件指针不在正确的位置,因为按这样写的代码会成功读取字符串'm'
并解包为'm'
。(我这里假设你用的是Python 2.x;3.x会有更多问题,但你根本不会走到这一步。)
对于你的具体用例(“文件头……包含关于内容的信息;版本信息和其他字符串值”),我建议直接用write
写字符串,并加上换行符。(如果字符串中可以有嵌入的换行符,你可以用反斜杠转义成\n
,使用C风格或RFC822风格的续行,或者用引号括起来等等。)
这样做有很多好处。首先,它使格式非常容易被人类读取(也容易编辑和调试)。而且,虽然有时这会占用更多空间,但一个单字符的终止符至少和长度前缀格式一样高效,甚至可能更高效。最后但同样重要的是,这意味着生成和解析头部的代码非常简单。
在后面的评论中,你澄清了你还想写整数,但这并不会改变什么。一个'i'
整数值会占用4个字节,但大多数应用程序写很多小数字,如果把它们当字符串写,只需要1-2个字节(再加1个字节作为终止符/分隔符)。而且如果你写的不是小数字,Python的int
可能会大到无法放入C的int
中——在这种情况下,struct
会默默溢出,只写低32位。