Python: Similar functionality in struct and array vs ctypes

Soc*_*cob 3 python arrays struct ctypes binary-data

Python provides the following three modules that deal with C types and how to handle them:

  • struct for C structs
  • array for arrays such as those in C
  • ctypes for C functions, which necessarily entails dealing with C’s type system

While ctypes seems more general and flexible (its main task being “a foreign function library for Python”) than struct and array, there seems to be significant overlap in functionality between these three modules when the task is to read binary data structures. For example, if I wanted to read a C struct

struct MyStruct {
    int a;
    float b;
    char c[12];
};
Run Code Online (Sandbox Code Playgroud)

I could use struct as follows:

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'
Run Code Online (Sandbox Code Playgroud)

On the other hand, using ctypes works equally well (although a bit more verbose):

 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'
Run Code Online (Sandbox Code Playgroud)

(Aside: I do wonder where the trailing '\0' went in this version, though…)

This seems to me like it violates the principles in “The Zen of Python”:

  1. There should be one—and preferably only one—obvious way to do it.

So how did this situation with several of these similar modules for binary data handling arise? Is there a historical or practical reason? (For example, I could imagine omitting the struct module entirely and simply adding a more convenient API for reading/writing C structs to ctypes.)

mat*_*cik 8

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

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

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

您还会注意到,它struct可以让您轻松地指定字节序,因为它以多种不同的方式处理二进制数据的打包和解包;而 inctypes获取非本地字节顺序更困难,因为它使用C 本地的字节顺序。

如果您的任务是读取二进制数据结构,那么抽象级别会越来越高:

  1. 手动分割字节数组,并与转换部int.from_bytes
  2. 用格式字符串描述数据并使用struct一次性解包
  3. 使用像Construct这样的库以逻辑术语声明性地描述结构。

ctypes甚至不要在这里计算,因为对于这项任务,使用ctypes几乎是通过不同的编程语言进行往返。它适用于您的示例这一事实是偶然的。它之所以有效,是因为 C 本身就适合表达多种打包二进制数据的方式。但是,例如,如果您的结构是混合端序的,则在ctypes. 另一个示例是半精度浮点数,它没有 C 等效项(请参阅此处)。

从这个意义上来说,ctypes使用也是很合理的struct——毕竟“二进制数据的打包和解包”是“与C接口”的一个子任务。

在另一方面,这将毫无意义struct来使用ctypes:它会像使用email库进行字符编码转换,因为它是一个任务,一个电子邮件库可以做到。

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