Python:struct 和 array 与 ctypes 中的相似功能
Python: Similar functionality in struct and array vs ctypes
Python提供了以下三个处理C类型的模块以及如何处理它们:
虽然 ctypes
看起来比 struct
和 array
更通用和灵活(它的主要任务是“Python 的外部函数库”),但似乎当任务是读取二进制数据结构时,这三个模块之间的功能明显重叠。例如,如果我想读取 C struct
struct MyStruct {
int a;
float b;
char c[12];
};
我可以使用 struct
如下:
a, b, c = struct.unpack('if12s', b'\x11[=12=][=12=][=12=]\x12\x34\x56\x78hello world[=12=]')
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[=13=][=13=][=13=]\x12\x34\x56\x78hello world[=13=]')
print(s.a, s.b, s.c)
# 17 1.7378244361449504e+34 b'hello world'
(旁白:我确实想知道尾随的 '[=22=]'
在这个版本中去了哪里……)
这在我看来似乎违反了“Python之禅”的原则:
- There should be one—and preferably only one—obvious way to do it.
那么,这些用于二进制数据处理的类似模块中的几个是如何出现这种情况的呢?有历史或实际原因吗? (例如,我可以想象完全省略 struct
模块并简单地为 reading/writing C 结构添加一个更方便的 API 到 ctypes
。)
免责声明:此 post 是基于我对 Python stdlib 中 "division of labor" 的理解的推测,而不是基于实际可参考信息。
您的问题源于 "C structs" 和 "binary data" 往往可以互换使用,虽然在实践中是正确的,但在技术意义上是错误的。 struct
文档也具有误导性:它声称可以在 "C structs" 上工作,而更好的描述是 "binary data",并带有一些关于 C 兼容性的免责声明。
从根本上说,struct
、array
和 ctypes
做不同的事情。 struct
处理将 Python 值转换为内存中的二进制格式。 array
处理有效地存储大量值。 ctypes
处理 C 语言 (*)。功能上的重叠源于这样一个事实,即对于 C,"binary in-memory formats" 是原生的,而 "efficiently storing values" 是 将它们打包到类似 C 的数组中。
您还会注意到 struct
可以让您轻松指定字节顺序,因为它以多种不同的方式处理二进制数据的打包和解包;而在 ctypes
中更难获得非本地字节顺序,因为它使用了 C.
本地的字节顺序
如果您的任务是读取二进制数据结构,则抽象级别会增加:
- 手动拆分字节数组并用
int.from_bytes
等转换部分
- 用格式字符串描述数据并使用
struct
一次性解包
- 使用像 Construct 这样的库以逻辑术语声明性地描述结构。
ctypes
甚至不考虑这里,因为对于这个任务,使用 ctypes
几乎是通过 不同的编程语言 .它对您的示例同样有效的事实是偶然的;它之所以有效,是因为 C 本身就适合表达多种打包二进制数据的方式。但是,如果您的结构是混合字节序的,例如,将很难用 ctypes
表示。另一个例子是半精度浮点数,它没有 C 等效项(参见 here)。
从这个意义上说,ctypes
使用struct
也是非常合理的——毕竟"packing and unpacking binary data"是"interfacing with C"的子任务。
另一方面,struct
使用 ctypes
没有任何意义:这就像使用 email
库进行字符编码转换一样,因为这是一项任务电子邮件图书馆可以做到。
(*) 嗯,基本上。更精确的是 "C-based environments",即由于与 C 作为主要系统语言的共同进化,现代计算机如何在低级别上工作。
Python提供了以下三个处理C类型的模块以及如何处理它们:
虽然 ctypes
看起来比 struct
和 array
更通用和灵活(它的主要任务是“Python 的外部函数库”),但似乎当任务是读取二进制数据结构时,这三个模块之间的功能明显重叠。例如,如果我想读取 C struct
struct MyStruct {
int a;
float b;
char c[12];
};
我可以使用 struct
如下:
a, b, c = struct.unpack('if12s', b'\x11[=12=][=12=][=12=]\x12\x34\x56\x78hello world[=12=]')
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[=13=][=13=][=13=]\x12\x34\x56\x78hello world[=13=]')
print(s.a, s.b, s.c)
# 17 1.7378244361449504e+34 b'hello world'
(旁白:我确实想知道尾随的 '[=22=]'
在这个版本中去了哪里……)
这在我看来似乎违反了“Python之禅”的原则:
- There should be one—and preferably only one—obvious way to do it.
那么,这些用于二进制数据处理的类似模块中的几个是如何出现这种情况的呢?有历史或实际原因吗? (例如,我可以想象完全省略 struct
模块并简单地为 reading/writing C 结构添加一个更方便的 API 到 ctypes
。)
免责声明:此 post 是基于我对 Python stdlib 中 "division of labor" 的理解的推测,而不是基于实际可参考信息。
您的问题源于 "C structs" 和 "binary data" 往往可以互换使用,虽然在实践中是正确的,但在技术意义上是错误的。 struct
文档也具有误导性:它声称可以在 "C structs" 上工作,而更好的描述是 "binary data",并带有一些关于 C 兼容性的免责声明。
从根本上说,struct
、array
和 ctypes
做不同的事情。 struct
处理将 Python 值转换为内存中的二进制格式。 array
处理有效地存储大量值。 ctypes
处理 C 语言 (*)。功能上的重叠源于这样一个事实,即对于 C,"binary in-memory formats" 是原生的,而 "efficiently storing values" 是 将它们打包到类似 C 的数组中。
您还会注意到 struct
可以让您轻松指定字节顺序,因为它以多种不同的方式处理二进制数据的打包和解包;而在 ctypes
中更难获得非本地字节顺序,因为它使用了 C.
如果您的任务是读取二进制数据结构,则抽象级别会增加:
- 手动拆分字节数组并用
int.from_bytes
等转换部分 - 用格式字符串描述数据并使用
struct
一次性解包 - 使用像 Construct 这样的库以逻辑术语声明性地描述结构。
ctypes
甚至不考虑这里,因为对于这个任务,使用 ctypes
几乎是通过 不同的编程语言 .它对您的示例同样有效的事实是偶然的;它之所以有效,是因为 C 本身就适合表达多种打包二进制数据的方式。但是,如果您的结构是混合字节序的,例如,将很难用 ctypes
表示。另一个例子是半精度浮点数,它没有 C 等效项(参见 here)。
从这个意义上说,ctypes
使用struct
也是非常合理的——毕竟"packing and unpacking binary data"是"interfacing with C"的子任务。
另一方面,struct
使用 ctypes
没有任何意义:这就像使用 email
库进行字符编码转换一样,因为这是一项任务电子邮件图书馆可以做到。
(*) 嗯,基本上。更精确的是 "C-based environments",即由于与 C 作为主要系统语言的共同进化,现代计算机如何在低级别上工作。