Python 位列表到字节列表

Python bit list to byte list

我有一个很长的整数 1 和 0 的一维列表,代表 8 位二进制字节。什么是 整洁 从中创建包含整数字节的新列表的方法。

我熟悉 C,但对 Python 不熟悉,所以我按照使用 C 的方式对其进行了编码:一种循环遍历每一位的精细结构。但是,我知道 Python 优于 C 的全部意义在于,通常可以紧凑而优雅地完成此类事情,我应该学习如何做到这一点。也许使用列表理解?

这行得通,但如能提供更 "Pythonic" 的建议,我们将不胜感激:

#!/usr/bin/env python2
bits = [1,0,0,1,0,1,0,1,0,1,1,0,1,0,1,1,1,1,1,0,0,1,1,1]
bytes = []
byt = ""
for bit in bits:
  byt += str(bit)
  if len(byt) == 8:
    bytes += [int(byt, 2)]
    byt = ""
print bytes

$ bits-to-bytes.py
[149, 107, 231]

您可以将列表分成 8 个元素的块并将子元素映射到 str:

[int("".join(map(str, bits[i:i+8])), 2) for i in range(0, len(bits), 8)]

您可以将其分成两部分映射和连接一次:

mapped = "".join(map(str, bits))
[int(mapped[i:i+8], 2) for i in range(0, len(mapped), 8)]

或者在itertools中使用iter并借鉴石斑鱼recipe

it = iter(map(str, bits))
[int("".join(sli), 2) for sli in zip(*iter([it] * 8))]

iter(map(str, bits)) 将位的内容映射到 str 并创建一个 iteratorzip(*iter([it] * 8)) 将元素分组为 8 个子元素的组。
每个 zip(*iter.. 从我们的迭代器中消耗八个子元素,所以我们总是得到顺序组,它与第一个代码中的切片逻辑相同,我们只是避免了切片的需要。

正如 Sven 评论的那样,对于不能被 n 整除的列表,您将使用类似于原始代码的 zip 丢失数据,您可以调整我链接的石斑鱼食谱来处理这些情况:

from itertools import zip_longest # izip_longest python2

bits = [1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1,1,0]
it = iter(map(str, bits))

print( [int("".join(sli), 2) for sli in izip_longest(*iter([it] * 8),fillvalue="")])
[149, 107, 231, 2] # using just zip would be  [149, 107, 231] 

fillvalue="" 意味着我们用空字符串填充奇数长度组,这样我们仍然可以调用 int("".join(sli), 2) 并获得正确的输出,如上所示,我们在 1,0 之后得到 3 * 8块。

在您自己的代码中 bytes += [int(byt, 2)] 可以简单地变成 bytes.append(int(byt, 2))

Padraic的解不错;这是另一种方法:

from itertools import izip_longest


def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # Taken from itertools recipes
    # https://docs.python.org/2/library/itertools.html#recipes
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

bits = [1, 0, 0, 1, 0, 1, 0, 1,
        0, 1, 1, 0, 1, 0, 1, 1,
        1, 1, 1, 0, 0, 1, 1, 1]

byte_strings = (''.join(bit_group) for bit_group in grouper(map(str, bits), 8))
bytes = [int(byte_string, 2) for byte_string in byte_strings]

print bytes  # [149, 107, 231]

由于您是从数字列表开始的,因此您可能希望避免字符串操作。这里有几个方法:

  • 将原始列表分成8位的块,计算每个字节的十进制值(假设位数是8的倍数);感谢 将序列划分为 8 个子元素组的好方法;

    bits = [1,0,0,1,0,1,0,1,0,1,1,0,1,0,1,1,1,1,1,0,0,1,1,1]
    [sum(b*2**x for b,x in zip(byte[::-1],range(8))) for byte in zip(*([iter(bits)]*8))]
    
  • 使用按位运算符(可能效率更高);如果位数不是 8 的倍数,则代码的工作方式就好像位序列 在左侧用 0 填充 (在左侧填充通常比在右侧填充更有意义, 因为它保留了原始二进制数字序列的数值)

    bits = [1,0,0,1,0,1,0,1,0,1,1,0,1,0,1,1,1,1,1,0,0,1,1,1]
    n = sum(b*2**x for b,x in zip(bits[::-1],range(len(bits)))) # value of the binary number represented by 'bits'
    # n = int(''.join(map(str,bits)),2) # another way of finding n by means of string manipulation
    [(n>>(8*p))&255 for p in range(len(bits)//8-(len(bits)%8==0),-1,-1)]