Python中字节字面量的比较

14 投票
3 回答
65014 浏览
提问于 2025-04-18 13:58

这个问题是因为我想用 bytes 字符串作为字典的键,而我认为相等的字节值却没有被当作相等来处理。

为什么下面这段 Python 代码比较后不相等呢?难道这两个不是同样的二进制数据表示吗?(这个例子特意选择了避免字节序的问题)

b'0b11111111' == b'0xff'

我知道下面这个是成立的,证明了它们是等价的:

int(b'0b11111111', 2) == int(b'0xff', 16)

但是为什么 Python 要我了解它们的表示方式呢?这和字节序有关吗?有没有简单的方法可以让它们比较时被认为是相等的,而不是把它们都转换成,比如说,十六进制字面量?有没有一种透明且清晰的方法可以在所有表示之间转换,且在某种程度上不依赖于平台(或者我是不是要求太多了)?

假设我想用 8 位的形式 b'0b11111111' 来索引一个字典,那为什么 Python 会把它扩展到十个字节,我该如何防止这种情况发生?

这是一个大型树形数据结构中的一小部分,把我的索引扩展到 80 倍似乎是巨大的内存浪费。

3 个回答

3

看起来你想要得到一个表示值 0b11111111(也就是255)的字节串。但 b'0b11111111' 并不是这个意思——它实际上是表示字符(Unicode)字符串 '0b11111111' 的字节串。

你想要的应该写成 b'\xff'。你可以检查一下,它确实是一个字节:len(b'\xff') == 1

如果你想把一个Python的 int 转换成二进制表示,可以使用 ctypes 这个库。你需要选择一种C语言的整数类型,比如:

>>> bytes(ctypes.c_ubyte(255))
b'\xff'

>>> bytes(ctypes.c_ubyte(0xff))
b'\xff'

>>> bytes(ctypes.c_long(255))
b'\xff\x00\x00\x00\x00\x00\x00\x00'

注意:你可以用别名 c_uint8(即8位无符号C整数)和 c_int64(64位有符号C整数)来代替 c_ubytec_long

如果要转换回去:

>>> ctypes.c_ubyte.from_buffer_copy(b'\xff').value
255

要小心溢出的问题:

>>> ctypes.c_ubyte(256)
c_ubyte(0)
6

b'0b11111111' 这个东西由 10 个字节组成:

In [44]: list(b'0b11111111')
Out[44]: ['0', 'b', '1', '1', '1', '1', '1', '1', '1', '1']

b'0xff' 这个东西只有 4 个字节:

In [45]: list(b'0xff')
Out[45]: ['0', 'x', 'f', 'f']

很明显,它们不是同一个东西。

Python 非常重视明确性。(明确比模糊好。)它不会 假设 b'0b11111111' 一定是一个整数的二进制表示。它只是一个字节字符串。你怎么理解它,必须明确说明。

16

字节可以表示任何东西。Python 不会也无法猜测你的字节可能编码的内容。

举个例子,int(b'0b11111111', 34)也是一种有效的解释,但这种解释和十六进制的FF是不一样的。

实际上,解释的方式是无穷无尽的。这些字节可以表示一系列的ASCII码,或者图像颜色,或者音乐音符。

在你明确给出解释之前,字节对象仅仅是由0到255范围内的值组成,而这些字节的文本表示如果可以被打印出来,就会使用ASCII:

>>> list(bytes(b'0b11111111'))
[48, 98, 49, 49, 49, 49, 49, 49, 49, 49]
>>> list(bytes(b'0xff'))
[48, 120, 102, 102]

这些字节序列是不相等的。

如果你想把这些序列明确地解释为整数常量,可以使用ast.literal_eval()来解释解码后的文本值;在比较之前,始终要先进行标准化:

>>> import ast
>>> ast.literal_eval(b'0b11111111'.decode('utf8'))
255
>>> ast.literal_eval(b'0xff'.decode('utf8'))
255

撰写回答