Python提供了以下三个模块来处理C类型以及如何处理它们:
尽管ctypes
比struct
和array
看起来更通用,更灵活(其主要任务是“ Python的外来函数库”),但两者之间在功能上有明显的重叠这三个模块的任务是读取二进制数据结构时。例如,如果我想读取C结构
struct MyStruct {
int a;
float b;
char c[12];
};
我可以按如下方式使用struct
:
a, b, c = struct.unpack('if12s', b'\x11\0\0\0\x12\x34\x56\x78hello world\0')
print(a, b, c)
# 17 1.7378244361449504e+34 b'hello world\x00'
另一方面,using ctypes
works equally well(虽然有些冗长):
class MyStruct(ctypes.Structure):
_fields_ = [
('a', ctypes.c_int),
('b', ctypes.c_float),
('c', ctypes.c_char * 12)
]
s = MyStruct.from_buffer_copy(b'\x11\0\0\0\x12\x34\x56\x78hello world\0')
print(s.a, s.b, s.c)
# 17 1.7378244361449504e+34 b'hello world'
(此外:我确实不知道结尾的'\0'
在此版本中的位置…)
在我看来,这似乎违反了“ Python的禅宗”中的原则:
- 应该有一种(最好只有一种)明显的方式。
那么,使用这些类似的模块中的几个进行二进制数据处理的情况如何出现?有历史或实际原因吗? (例如,我可以想象完全省略struct
模块,而只是添加一个更方便的API来向ctypes
读/写C结构。)
答案 0 :(得分:2)
免责声明:这篇文章是基于我对Python stdlib中“分工”的理解,而不是基于事实可参考的信息。
您的问题源于以下事实:“ C结构”和“二进制数据”往往可以互换使用,尽管在实践中是正确的,但从技术意义上讲是错误的。 struct
文档也具有误导性:它声称可以在“ C结构”上工作,而更好的描述是“二进制数据”,其中有一些关于C兼容性的免责声明。
从根本上说,struct
,array
和ctypes
做不同的事情。 struct
处理将Python值转换为二进制内存格式。 array
处理有效存储大量值的问题。 ctypes
处理C语言 (*)。功能上的重叠源于以下事实:对于C,“二进制内存格式”是本机的,并且“有效存储值” 将它们包装到C形数组中。
您还将注意到,struct
使您可以轻松指定字节序,因为它以许多可以打包的不同方式处理二进制数据的打包和解包。而在ctypes
中,获取非本机字节顺序会更加困难,因为它使用了C 固有的字节顺序。
如果您的任务是读取二进制数据结构,则抽象级别不断提高:
int.from_bytes
等转换部分struct
一次解压缩 ctypes
甚至都不在这里,因为对于此任务,使用ctypes
几乎要遍历不同的编程语言。它对您的示例同样适用的事实是偶然的;之所以起作用,是因为C本身就适合于表达打包二进制数据的许多方式。但是,例如,如果您的结构是混合字节序的,则很难在ctypes
中表示。另一个示例是没有C等效项的半精度浮点数(请参见here)。
从这个意义上讲,ctypes
使用struct
是非常合理的-毕竟,“打包和解压缩二进制数据”是“与C接口”的子任务。
另一方面,struct
使用ctypes
毫无意义:就像使用email
库进行字符编码转换一样,因为这是一个任务-邮件库可以做到。
(*)好,基本上。更精确的是类似“基于C的环境”,即由于以C作为主要系统语言的共同进化,现代计算机如何在低层工作。