Python array.byteswap:数字顺序没有倒过来,为什么每个元素的值都变了

前天 3阅读

把一段二进制数据交给array.frombytes,能成功往返成相同字节,并不说明读出的整数符合发送方的含义。array采用本机元素表示;byteswap可以交换每个元素内部的字节,但它会原地改值,也不会把数组元素顺序整体倒过来。需要分别检查数值、字节和返回值。

下面程序在小端Linux、CPython 3.12.14实跑。保存为demo.py,用python3 demo.py运行,只操作四个固定输入字节。H表示无符号短整数,本例首先要求其itemsize恰为2;遇到其他布局时明确停止,而不是把平台假设藏在结果里。

Python array.byteswap:数字顺序没有倒过来,为什么每个元素的值都变了

AI模型生成概念图:两组色块各自在组内交换先后,左右两组的位置保持不变,用于比喻逐元素字节交换;不是内存地址的真实布局图。

让四个字节各不相同

输入选择01、02、03、04,避免重复字节让错误字节序也得到相同结果。第一段按本机顺序计算独立预期,再调用byteswap;第二段从同一输入重新建数组,根据主机是否小端决定是否转换,目标是把它解释为两项大端整数。

完整可运行程序

import sys
from array import array

packet = bytes.fromhex("01 02 03 04")
values = array("H")
if values.itemsize != 2:
    raise RuntimeError("this experiment requires a two-byte unsigned short")
values.frombytes(packet)
native = [int.from_bytes(packet[i:i + 2], sys.byteorder) for i in (0, 2)]
assert values.tolist() == native
assert values.tobytes() == packet
print("host byte order:", sys.byteorder)
print("native values:", values.tolist())
print("native round trip:", values.tobytes().hex())

opposite = "big" if sys.byteorder == "little" else "little"
expected_swapped = [int.from_bytes(packet[i:i + 2], opposite) for i in (0, 2)]
result = values.byteswap()
assert result is None
assert values.tolist() == expected_swapped
assert values.tobytes() == bytes.fromhex("02 01 04 03")
print("byteswap return:", result)
print("swapped values:", values.tolist())
print("swapped bytes:", values.tobytes().hex())

network_values = array("H")
network_values.frombytes(packet)
if sys.byteorder == "little":
    network_values.byteswap()
assert network_values.tolist() == [258, 772]
print("big-endian interpretation:", network_values.tolist())
network_values.frombytes(bytes.fromhex("05 00"))
assert len(network_values) == 3
print("frombytes appends count:", len(network_values))
try:
    array("H").frombytes(b"\x01")
except ValueError:
    print("partial item:", "ValueError")
else:
    raise AssertionError("one byte cannot make a two-byte element")

本次实际输出(以下为结果,不是程序)

host byte order: little
native values: [513, 1027]
native round trip: 01020304
byteswap return: None
swapped values: [258, 772]
swapped bytes: 02010403
big-endian interpretation: [258, 772]
frombytes appends count: 3
partial item: ValueError

字节往返相同,解释出的数字仍可能不对

本机输出little,直接frombytes得到513与1027,因为两个字节分别按小端组合。与此同时native round trip仍是01020304,原始字节完全没变。这个对照说明,往返测试只能证明当前编码与解码互相配合,不能单独证明使用了外部协议需要的字节序。

若对端约定大端,同样的01 02应解释为258,03 04应解释为772。代码用int.from_bytes按本机顺序计算初始预期,而不是把小端结果写成所有机器都应输出的常量,因此前半段能明确暴露平台差异。文章中的实际输出则只对应这台小端机器。

byteswap修改数组,返回值是None

交换后数值成为258、772,字节变成02010403。变化发生在每个两字节元素内部:01 02变成02 01,03 04变成04 03;两个元素的前后位置没有交换。若把整段字节直接反转,就会得到04030201,含义已经不同。

byteswap return输出None,说明不能写成values = values.byteswap()再把values当数组使用。该方法已经改动原数组。若还有其他引用指向同一个数组,它们随后看到的也是改动后的数值;需要同时保留转换前后版本时,应明确创建另一份数组,而不是假定方法会返回副本。

先知道来源顺序,再决定是否交换

network_values重新读取原始输入,只在本机是小端时交换,因此最后稳定得到大端含义的258与772。如果本来就在大端机器上无条件再交换一次,反而会把正确解释变错。byteswap不会探测来源格式,决定是否调用仍需要可靠的格式约定。

这也不是给数组永久附加一个“大端模式”。转换完成后,数组继续按本机布局存放数值;tobytes输出的仍是当前机器表示。若下一步要按固定大端格式传出去,应重新按照输出协议编码,不能因为曾经交换过就假定后续所有序列化自动遵守来源字节序。

追加与元素宽度还要分别检查

frombytes的名字容易让人联想到用新内容替换整个数组,但再次调用后长度变为3,它追加了一项。程序刻意只检查追加后的数量,没有把新加字节的数值包装成已经完成统一转换;连续接收多个数据块时,应对每块使用一致的来源字节序处理策略。

最后只传一个字节给两字节H数组,触发ValueError。单个输入块必须满足完整元素宽度,frombytes不会像增量文本解码器一样替调用者保存半个元素等待下次补齐。真实流式读取需要先在外层累计足够字节,再分批交给数组,并在结束时检查剩余字节。

这个例子不测性能,也不建议把所有协议字段都直接搬进array。字段宽度各异、有版本号或校验规则时,需要更明确的结构解析。array适合已经确认类型和布局的一组同类数值,验收仍应同时包含itemsize、元素数量、来源字节序和实际输出字节。

参考资料

资料核验日期:2026年10月2日。以上输出来自文中固定输入的本地实跑,程序退出码为0。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。