Python:结构和数组与ctypes类似的功能

时间:2018-08-24 12:15:43

标签: python arrays struct ctypes binary-data

Python提供了以下三个模块来处理C类型以及如何处理它们:

  • struct用于C结构
  • array用于C语言中的数组
  • ctypes用于C函数,这必然需要处理C的类型系统

尽管ctypesstructarray看起来更通用,更灵活(其主要任务是“ 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的禅宗”中的原则:

  
      
  1. 应该有一种(最好只有一种)明显的方式。
  2.   

那么,使用这些类似的模块中的几个进行二进制数据处理的情况如何出现?有历史或实际原因吗? (例如,我可以想象完全省略struct模块,而只是添加一个更方便的API来向ctypes读/写C结构。)

1 个答案:

答案 0 :(得分:2)

免责声明:这篇文章是基于我对Python stdlib中“分工”的理解,而不是基于事实可参考的信息。

您的问题源于以下事实:“ C结构”和“二进制数据”往往可以互换使用,尽管在实践中是正确的,但从技术意义上讲是错误的。 struct文档也具有误导性:它声称可以在“ C结构”上工作,而更好的描述是“二进制数据”,其中有一些关于C兼容性的免责声明。

从根本上说,structarrayctypes 做不同的事情struct处理将Python值转换为二进制内存格式。 array处理有效存储大量值的问题。 ctypes处理C语言 (*)。功能上的重叠源于以下事实:对于C,“二进制内存格式”是本机的,并且“有效存储值” 将它们包装到C形数组中。

您还将注意到,struct使您可以轻松指定字节序,因为它以许多可以打包的不同方式处理二进制数据的打包和解包。而在ctypes中,获取非本机字节顺序会更加困难,因为它使用了C 固有的字节顺序

如果您的任务是读取二进制数据结构,则抽象级别不断提高:

  1. 手动拆分字节数组并使用int.from_bytes等转换部分
  2. 使用格式字符串描述数据,并使用struct一次解压缩
  3. 使用Construct之类的库以逻辑术语声明性地描述该结构。

ctypes甚至都不在这里,因为对于此任务,使用ctypes几乎要遍历不同的编程语言。它对您的示例同样适用的事实是偶然的;之所以起作用,是因为C本身就适合于表达打包二进制数据的许多方式。但是,例如,如果您的结构是混合字节序的,则很难在ctypes中表示。另一个示例是没有C等效项的半精度浮点数(请参见here)。

从这个意义上讲,ctypes使用struct是非常合理的-毕竟,“打包和解压缩二进制数据”是“与C接口”的子任务。

另一方面,struct使用ctypes毫无意义:就像使用email库进行字符编码转换一样,因为这是一个任务-邮件库可以做到。

(*)好,基本上。更精确的是类似“基于C的环境”,即由于以C作为主要系统语言的共同进化,现代计算机如何在低层工作。