【问题标题】:python bytes to bit stringpython字节到位字符串
【发布时间】:2020-06-20 01:58:33
【问题描述】:

我有 bytes 类型的值需要转换为 BIT STRING

bytes_val = (b'\x80\x00', 14)

索引零中的字节需要转换为长度由第二个元素(在本例中为 14)指示的位串,并格式化为如下所示的 8 位组。

预期输出 => '10000000 000000'B

另一个例子

bytes_val2 = (b'\xff\xff\xff\xff\xf0\x00', 45) #=> '11111111 11111111 11111111 11111111 11110000 00000'B

【问题讨论】:

标签: python byte bitstring


【解决方案1】:

格式的一些组合(下面是 f-string,但可以用其他方式)和切片:

def bytes2binstr(b, n=None):
    s = ' '.join(f'{x:08b}' for x in b)
    return s if n is None else s[:n + n // 8 + (0 if n % 8 else -1)]

如果我理解正确(我不确定最后的 B 应该是什么意思),它会通过您的测试和更多测试:

func = bytes2binstr
args = (
    (b'\x80\x00', None),
    (b'\x80\x00', 14),
    (b'\x0f\x00', 14),
    (b'\xff\xff\xff\xff\xf0\x00', 16),
    (b'\xff\xff\xff\xff\xf0\x00', 22),
    (b'\x0f\xff\xff\xff\xf0\x00', 45),
    (b'\xff\xff\xff\xff\xf0\x00', 45),
)
for arg in args:
    print(arg)
    print(repr(func(*arg)))
# (b'\x80\x00', None)
# '10000000 00000000'
# (b'\x80\x00', 14)
# '10000000 000000'
# (b'\x0f\x00', 14)
# '00001111 000000'
# (b'\xff\xff\xff\xff\xf0\x00', 16)
# '11111111 11111111'
# (b'\xff\xff\xff\xff\xf0\x00', 22)
# '11111111 11111111 111111'
# (b'\x0f\xff\xff\xff\xf0\x00', 45)
# '00001111 11111111 11111111 11111111 11110000 00000'
# (b'\xff\xff\xff\xff\xf0\x00', 45)
# '11111111 11111111 11111111 11111111 11110000 00000'

说明

  • 我们从bytes 对象开始
  • 遍历它会给我们一个字节作为数字
  • 每个字节都是 8 位,所以解码已经可以给我们正确的分离
  • 每个字节都使用b 二进制说明符进行格式化,并带有一些额外的格式:0 零填充,8 最小长度
  • 我们使用' ' 作为“分隔符”连接(连接)格式化结果
  • 如果未指定n 的最大位数(设置为None),则最终返回结果,否则将结果裁剪为n + 中间添加的空格数8 个字符组。

在上面的解决方案中8 有点硬编码。 如果您希望它成为一个参数,您可能需要使用int.from_bytes() 查看(可能是)@kederrac first answer 的变体。 这可能类似于:

def bytes2binstr_frombytes(b, n=None, k=8):
    s = '{x:0{m}b}'.format(m=len(b) * 8, x=int.from_bytes(b, byteorder='big'))[:n]
    return ' '.join([s[i:i + k] for i in range(0, len(s), k)])

它给出与上面相同的输出。

在速度方面,基于int.from_bytes() 的解决方案也更快:

for i in range(2, 7):
    n = 10 ** i
    print(n)
    b = b''.join([random.randint(0, 2 ** 8 - 1).to_bytes(1, 'big') for _ in range(n)])
    for func in funcs:
        print(func.__name__, funcs[0](b, n * 7) == func(b, n * 7))
        %timeit func(b, n * 7)
    print()
# 100
# bytes2binstr True
# 10000 loops, best of 3: 33.9 µs per loop
# bytes2binstr_frombytes True
# 100000 loops, best of 3: 15.1 µs per loop

# 1000
# bytes2binstr True
# 1000 loops, best of 3: 332 µs per loop
# bytes2binstr_frombytes True
# 10000 loops, best of 3: 134 µs per loop

# 10000
# bytes2binstr True
# 100 loops, best of 3: 3.29 ms per loop
# bytes2binstr_frombytes True
# 1000 loops, best of 3: 1.33 ms per loop

# 100000
# bytes2binstr True
# 10 loops, best of 3: 37.7 ms per loop
# bytes2binstr_frombytes True
# 100 loops, best of 3: 16.7 ms per loop

# 1000000
# bytes2binstr True
# 1 loop, best of 3: 400 ms per loop
# bytes2binstr_frombytes True
# 10 loops, best of 3: 190 ms per loop

【讨论】:

  • 字符串格式化有一个非常实用的整数二进制格式化选项,不需要对前缀进行切片。为什么不使用它?
  • @MartijnPieters 我假设>0 说明符不适用于b,但我错了。感谢您的改进!
  • 您根本不需要>,它是数字的默认对齐方式(bytes 对象b 中的每个x 都是区间[0, 256) 中的整数)。
【解决方案2】:

你可以使用:

def bytest_to_bit(by, n):
    bi = "{:0{l}b}".format(int.from_bytes(by, byteorder='big'), l=len(by) * 8)[:n]
    return ' '.join([bi[i:i + 8] for i in range(0, len(bi), 8)])

bytest_to_bit(b'\xff\xff\xff\xff\xf0\x00', 45)

输出:

'11111111 11111111 11111111 11111111 11110000 00000'

步骤:

  1. 使用int.from_bytes将字节转换为整数

  2. str.format 方法可以采用binary format spec.


此外,您可以使用更紧凑的形式,其中每个字节都被格式化:

def bytest_to_bit(by, n):
    bi = ' '.join(map('{:08b}'.format, by))
    return bi[:n + len(by) - 1].rstrip()

bytest_to_bit(b'\xff\xff\xff\xff\xf0\x00', 45)

【讨论】:

  • 最终,您可能需要一些额外的格式来处理二进制表示不以1 开头的输入的极端情况。但是,问题中没有指定,所以不是 100% 确定,但是例如如果所需的输出必须有前导 0s,(b'\0f\00', 14) 将不会很好地发挥作用。
  • 如果没有最小宽度,前导零将从您的输出中删除,因此(b'\x00\x00', 16) 生成'0',而不是'0000000000000000'。你会想使用"{:0{l}b}".format(int.from_bytes(by, byteorder='big'), l=len(by) * 8)[:n]
【解决方案3】:
test_data = [
    (b'\x80\x00', 14),
    (b'\xff\xff\xff\xff\xf0\x00', 45),
]


def get_bit_string(bytes_, length) -> str:
    output_chars = []
    for byte in bytes_:
        for _ in range(8):
            if length <= 0:
                return ''.join(output_chars)
            output_chars.append(str(byte >> 7 & 1))
            byte <<= 1
            length -= 1
        output_chars.append(' ')
    return ''.join(output_chars)


for data in test_data:
    print(get_bit_string(*data))

输出:

10000000 000000
11111111 11111111 11111111 11111111 11110000 00000

解释:

  • length:从目标长度开始,递减到0
  • if length &lt;= 0: return ...:如果我们达到目标长度,停止并返回。
  • ''.join(output_chars):从列表中创建字符串。
  • str(byte &gt;&gt; 7 &amp; 1)
    • byte &gt;&gt; 7:右移 7 位(只保留 MSB,因为字节有 8 位。)
    • MSB 表示最高有效位
    • (...) &amp; 1按位和操作。它提取 LSB。
  • byte &lt;&lt;= 1:将byte左移1位。
  • length -= 1:减少length

【讨论】:

  • 能否请您添加一些关于不同行的作用的描述? - 谢谢!!
  • 我附上了解释:)
【解决方案4】:

这是懒惰的版本。
它既不加载也不处理整个字节。
无论输入大小如何,这个都会停止
其他解决方案可能不行!

我使用collections.deque 构建位串。

from collections import deque
from itertools import chain, repeat, starmap
import os  

def bit_lenght_list(n):
    eights, rem = divmod(n, 8)
    return chain(repeat(8, eights), (rem,))


def build_bitstring(byte, bit_length):
    d = deque("0" * 8, 8)
    d.extend(bin(byte)[2:])
    return "".join(d)[:bit_length]


def bytes_to_bits(byte_string, bits):
    return "{!r}B".format(
        " ".join(starmap(build_bitstring, zip(byte_string, bit_lenght_list(bits))))
    )

测试;

In [1]: bytes_ = os.urandom(int(1e9)) 

In [2]: timeit bytes_to_bits(bytes_, 0)                                                                                                                   
4.21 µs ± 27.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


In [3]: timeit bytes_to_bits(os.urandom(1), int(1e9))                                                                                                 
6.8 µs ± 51 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: bytes_ = os.urandom(6)                                                                                                                        

In [5]: bytes_                                                                                                                                       
Out[5]: b'\xbf\xd5\x08\xbe$\x01'

In [6]: timeit bytes_to_bits(bytes_, 45)  #'10111111 11010101 00001000 10111110 00100100 00000'B                                                                                                        
12.3 µs ± 85 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


In [7]:  bytes_to_bits(bytes_, 14)                                                                                                                   
Out[7]: "'10111111 110101'B"

【讨论】:

    【解决方案5】:

    当您说 BIT 时,您的意思是二进制? 我会试试的

    bytes_val = b'\\x80\\x00'
    
    for byte in bytes_val:
        value_in_binary = bin(byte)
    

    【讨论】:

    • BIT String - 二进制的字符串表示形式。
    【解决方案6】:

    这给出了没有python二进制表示前缀0b的答案:

    bit_str = ' '.join(bin(i).replace('0b', '') for i in bytes_val)
    

    【讨论】:

    • 获取TypeError: 'bytes' object cannot be interpreted as an integer
    • 那是 Python 2.x 还是 3.x?
    • &gt;&gt;&gt; sys.version_info sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0) &gt;&gt;&gt; bytes_val = (b'\x80\x00', 14) &gt;&gt;&gt; ' '.join(bin(i).replace('0b', '') for i in bytes_val) Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; File "&lt;stdin&gt;", line 1, in &lt;genexpr&gt; TypeError: 'bytes' object cannot be interpreted as an integer &gt;&gt;&gt;
    【解决方案7】:

    这适用于 Python 3.x:

    def to_bin(l):
        val, length = l
        bit_str = ''.join(bin(i).replace('0b', '') for i in val)
        if len(bit_str) < length:
            # pad with zeros
            return '0'*(length-len(bit_str)) + bit_str
        else:
            # cut to size
            return bit_str[:length]
    
    bytes_val = [b'\x80\x00',14]
    print(to_bin(bytes_val))
    

    这在 2.x 中有效:

    def to_bin(l):
        val, length = l
        bit_str = ''.join(bin(ord(i)).replace('0b', '') for i in val)
        if len(bit_str) < length:
            # pad with zeros
            return '0'*(length-len(bit_str)) + bit_str
        else:
            # cut to size
            return bit_str[:length]
    
    bytes_val = [b'\x80\x00',14]
    print(to_bin(bytes_val))
    

    两者都产生结果00000100000000

    【讨论】:

      猜你喜欢
      • 2021-03-27
      • 2019-02-22
      • 1970-01-01
      • 2019-05-19
      • 1970-01-01
      • 2016-03-28
      • 1970-01-01
      • 1970-01-01
      • 2012-04-21
      相关资源
      最近更新 更多