在Python中不使用外部模块将二进制信息转换为常规数据类型

1 投票
5 回答
980 浏览
提问于 2025-04-15 20:56

我现在的任务是读取一个格式很糟糕的二进制文件,并提取里面的变量。虽然我需要用C++(具体来说是ROOT)来完成这个任务,但我觉得用Python更容易理解,所以我决定先用Python实现,然后再转到C++。这样的话,使用一些简单的Python模块也不会让我在后续的C++编写中遇到太大问题。

基本上,我是这样做的:

In [5]: some_value
Out[5]: '\x00I'

In [6]: ''.join([str(ord(i)) for i in some_value])
Out[6]: '073'

In [7]: int(''.join([str(ord(i)) for i in some_value]))
Out[7]: 73

我知道肯定有更好的方法。你觉得呢?

补充说明:

关于这个二进制格式的一些信息。

链接1 http://grab.by/3njm 链接2 http://grab.by/3njv 链接3 http://grab.by/3nkL

这是我正在使用的字节序测试:

# Read a uint32 for endianess
endian_test = rq1_file.read(uint32)
if endian_test == '\x04\x03\x02\x01':
    print "Endian test: \\x04\\x03\\x02\\x01"
    swapbits = True
elif endian_test == '\x01\x02\x03\x04':
    print "Endian test: \\x01\\x02\\x03\\x04"
    swapbits = False

5 个回答

2

当然可以!请看下面的内容:

在编程中,有时候我们需要处理一些数据,比如从一个地方获取数据,然后把它放到另一个地方。这就像把水从一个杯子倒到另一个杯子一样。

有些时候,我们会用到一些工具或者库,这些工具就像是厨房里的器具,帮助我们更方便地完成任务。比如,有的工具可以让我们更快地找到需要的数据,或者更简单地处理这些数据。

在写代码的时候,我们可能会遇到一些错误,这就像在做饭时不小心把盐放多了。这个时候,我们需要检查代码,找出问题所在,然后修复它。

总之,编程就像是在解决一个个小问题,通过不断尝试和调整,最终达到我们想要的结果。

import struct
result, = struct.unpack('>H', some_value)
2

你实际上是在计算一个“256进制的数字”,这可以看作是一个多项式,所以可以用霍纳法则来处理:

>>> v = 0
>>> for c in someval: v = v * 256 + ord(c)

更常见的做法是使用位操作,而不是直接的算术运算——下面的代码是等价的:

>>> v = 0
>>> for c in someval: v = v << 8 | ord(c)
2

你的 int(''.join([str(ord(i)) for i in some_value])) 这个方法只有在除了最后一个字节之外,其他字节都是零的情况下才有效。
举个例子:
'\x01I' 应该是 1 * 256 + 73 = 329;但你得到的是 173。
'\x01\x02' 应该是 1 * 256 + 2 = 258;但你得到的是 12。
'\x01\x00' 应该是 1 * 256 + 0 = 256;但你得到的是 10。

这个方法还假设整数是以 大端 的方式存储的;你确认过这个假设吗?你确定 '\x00I' 代表的是整数 73,而不是 73 * 256 + 0 = 18688(或者其他什么)吗?请告诉我们你使用的电脑品牌和型号,以及操作系统,以帮助我们验证这个假设。

负整数是怎么表示的?

你需要处理 浮点数 吗?

写成 C++ 的要求是不可变的吗?“(ROOT, specifically)” 是什么意思?

如果唯一的要求是常识,那么推荐的顺序是:

  1. 用 Python 写,使用 struct 模块。

  2. 用 C++ 写,但使用 C++ 的库函数(特别是涉及浮点数时)。不要重复造轮子。

  3. 在 C++ 中自己写转换程序。你可以参考 Python struct 模块的 C 源码

更新

在文件格式细节发布后的评论:

  1. 字节序标记显然是可选的,除了在文件开始的位置。这有点不靠谱;它依赖于如果没有这个标记,块的第 3 和第 4 个字节就是头字符串的前两个字节,而 '\x03\x04''\x02\x01' 都不能有效地作为头字符串的开头。聪明的做法是读取六个字节——如果前四个是字节序标记,接下来的两个是头长度,然后再读取头字符串;否则就向后退四个字节再读取头字符串。

  2. 上面提到的情况属于麻烦类别。负长度是个真正的问题,因为它指定了一个最大长度,但没有说明实际长度是怎么确定的。它说“条目的实际大小是逐行给出的”。怎么给出?没有说明“数据行”是什么样子的。描述中提到“行”很多次;这些行是以回车和/或换行符结束的吗?如果是的话,怎么区分换行符字节和当前“数据行”中属于 uint16 的第一个字节?如果没有换行符,怎么知道当前的数据行什么时候结束?每个变量或其切片前面都有 uintNN 大小吗?

  3. 然后它说上面的(2)也适用于头字符串。真让人费解。你有关于“负长度”的示例吗(在文件布局的文档中,或在实际文件中)?(a)头字符串(b)数据“行”?

  4. 这个“确定格式”是公开的吗,比如在网上有文档吗?这个格式有可搜索的名称吗?你确定你是世界上第一个想要读取这个格式的人吗?

  5. 即使有完整的规范,读取这个文件格式也不是一件简单的事,即使是对二进制格式有经验的人,尤其是对 Python 也有经验的人(顺便说一下,Python 没有 float128)。你为这个任务分配了多少人小时?延迟和失败的惩罚是什么?

  6. 你最初的问题是关于修复你尝试解析 uint16 的有趣方法——做更多的事情远远超出了 SO 问题的范围和意图。

撰写回答