在 Python 3 中将二进制字符串转换为字节数组

Posted

技术标签:

【中文标题】在 Python 3 中将二进制字符串转换为字节数组【英文标题】:Convert binary string to bytearray in Python 3 【发布时间】:2015-12-16 23:59:31 【问题描述】:

尽管有许多相关问题,但我找不到任何与我的问题相匹配的问题。我想将二进制字符串(例如,"0110100001101001")更改为字节数组(同样的示例,b"hi")。

我试过这个:

bytes([int(i) for i in "0110100001101001"])

但我得到了:

b'\x00\x01\x01\x00\x01' #... and so on

在 Python 3 中执行此操作的正确方法是什么?

【问题讨论】:

相关:Convert binary to ASCII and vice versa 【参考方案1】:

下面是 Patrick 提到的第一种方式的示例:将位串转换为 int 并一次取 8 位。这样做的自然方式以相反的顺序生成字节。为了让字节恢复到正确的顺序,我在字节数组上使用扩展切片表示法,步长为 -1:b[::-1]

def bitstring_to_bytes(s):
    v = int(s, 2)
    b = bytearray()
    while v:
        b.append(v & 0xff)
        v >>= 8
    return bytes(b[::-1])

s = "0110100001101001"
print(bitstring_to_bytes(s))

显然,Patrick 的第二种方式更为紧凑。 :)

不过,在 Python 3 中有一个更好的方法:使用 int.to_bytes 方法:

def bitstring_to_bytes(s):
    return int(s, 2).to_bytes((len(s) + 7) // 8, byteorder='big')

如果len(s)保证是8的倍数,那么.to_bytes的第一个参数可以简化:

return int(s, 2).to_bytes(len(s) // 8, byteorder='big')

如果len(s) 不是 8 的倍数,这将引发OverflowError,这在某些情况下可能是可取的。


另一种选择是使用双重否定来执行天花板除法。对于整数 a 和 b,使用 // 进行地板除法

n = a // b

给出整数 n 使得 n 例如,47 // 10 给出 4,并且

-47 // 10 给出 -5。所以

-(-47 // 10) 给出 5,有效地执行天花板除法。

因此,在bitstring_to_bytes 我们可以这样做:

return int(s, 2).to_bytes(-(-len(s) // 8), byteorder='big')

但是,没有多少人熟悉这种高效而紧凑的习语,因此通常认为它的可读性不如

return (s, 2).to_bytes((len(s) + 7) // 8, byteorder='big')

【讨论】:

len(s) // 8 可能会失败,use (len(s) + 7) // 8 instead。 int.to_bytes 本质上是第一种方法——只是在 C 中比在 python 中更有效。 @J.F.Sebastian:好点;您的代码更健壮,我的假设输入位串已正确构造。另一种计算长度不是 8 整数倍的位串的正确大小的方法是使用“天花板除法”技巧:-(-len(s) // 8) '1001' 对应于b'\t' 与其他位串一样正确。除法技巧有效,但可读性较差(有些语言朝零舍入,有些朝负无穷大)。 感谢您的回答! *** 是一个了不起的资源。这将花费我更长的时间来使用文档(而且我可能不会偶然发现正确的功能)。 :)【参考方案2】:

您必须将其转换为 int 并一次获取 8 位,或者将其切成 8 字节长的字符串,然后将它们中的每一个转换为 int。在 Python 3 中,正如 PM 2Ring 和 J.F Sebastian 的回答所示,intto_bytes() 方法允许您非常有效地执行第一种方法。这在 Python 2 中不可用,因此对于坚持这一点的人来说,第二种方法可能更有效。这是一个例子:

>>> s = "0110100001101001"
>>> bytes(int(s[i : i + 8], 2) for i in range(0, len(s), 8))
b'hi'

为了打破这一点,range 语句从索引 0 开始,并为我们提供源字符串的索引,但一次推进 8 个索引。由于s 有 16 个字符长,它会给我们两个索引:

>>> list(range(0, 50, 8))
[0, 8, 16, 24, 32, 40, 48]
>>> list(range(0, len(s), 8))
[0, 8]

(我们在这里使用list() 来显示将从 Python 3 中的范围迭代器中检索到的值。)

然后,我们可以在此基础上通过截取 8 个字符长的片段来拆分字符串:

>>> [s[i : i + 8] for i in range(0, len(s), 8)]
['01101000', '01101001']

然后我们可以将它们中的每一个转换为整数,以 2 为底:

>>> list(int(s[i : i + 8], 2) for i in range(0, len(s), 8))
[104, 105]

最后,我们将整个内容包裹在bytes() 中以获得答案:

>>> bytes(int(s[i : i + 8], 2) for i in range(0, len(s), 8))
b'hi'

【讨论】:

@KevinGuan 解释已添加。如果满足您的需求,请采纳答案。 @KevinGuan 对不起,没注意! :-) 没有必要复杂和低效,这里有一个simpler solution @J.F.Sebastian -- 很好的观点。我通常停留在 Python 2 上,有时会忘记 Python 3 的增强功能。 感谢这个很棒的答案——如果有人想用 python 2 解决这个问题,这就是他们需要的答案。【参考方案3】:
>>> zero_one_string = "0110100001101001"
>>> int(zero_one_string, 2).to_bytes((len(zero_one_string) + 7) // 8, 'big')
b'hi'

它返回bytes 对象,它是一个不可变的字节序列。如果你想得到一个bytearray——一个可变的字节序列——那么只需调用bytearray(b'hi')

【讨论】:

谢谢!这(可能)是所有三个答案中最安全的,并且最明确地针对 python3。

以上是关于在 Python 3 中将二进制字符串转换为字节数组的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 3 中将字节转换为十六进制字符串的正确方法是啥?

在Python 3中将十六进制字符串(python 2)转换为带有十六进制数字的字节

请教网络高手们,windows驱动中将网络字节序转换为点分十进制数串的函数叫啥名字?

在 Python 中将十六进制字符串转换为字节列表

在 Python 中将整数转换为 2 字节的十六进制值

在Python中将IP地址字符串转换为二进制