Python位列表转字节列表

4
我有一个长的一维列表,其中包含整数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]
3个回答

4
你可以将列表切成8个元素一组的块,并将子元素映射为字符串:
[int("".join(map(str, bits[i:i+8])), 2) for i in range(0, len(bits), 8)]

您可以将其拆分为两个部分:映射(mapping)和连接(joining):

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

或者使用迭代器,借鉴itertools中的recipe

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

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

正如Sven所评论的那样,对于不可被n整除的列表,使用zip会丢失数据,类似于您的原始代码,您可以改编我链接的grouper配方来处理这些情况:

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] 
< p > 在这里,fillvalue="" 表示我们用空字符串填充奇数长度的组,以便我们仍然可以调用 int("".join(sli), 2) 并得到正确的输出,就像上面例子中取 3 * 8 段后仍剩下 1,0 一样。

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


啊,我看到你也想到了 grouper() :)。 - Cyphase
@PadraicCunningham:如果列表的长度不是8的倍数,您的版本会丢弃多余的位,因为zip()会在最短的序列上停止。 - Sven Marnach
@SvenMarnach,OP的代码也是这样的。我添加了一个链接到分组器配方,可以用于奇数长度,不太清楚OP在那种情况下想做什么。 - Padraic Cunningham
我知道,但是你之前的评论似乎暗示着分组方法可以处理奇数列表长度。 - Sven Marnach
1
谢谢。这是我问题最全面的答案。 - Dave Rove
显示剩余2条评论

1

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的列表,那么你应该传入一个与fillvalue不同的值,否则你会得到一个像100101NoneNone这样的字符串,这将导致int()函数出错。 - Sven Marnach

0

由于你从一个数字列表开始,可能希望避免字符串操作。这里有几种方法:

  • 将原始列表分成8位块,并计算每个字节的十进制值(假设位数是8的倍数);感谢Padraic Cunningham提供了一种很好的方法,可以将序列按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)]
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接