python байтов в битовую строку - PullRequest
3 голосов
/ 07 марта 2020

У меня есть значение типа 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

Ответы [ 7 ]

4 голосов
/ 08 апреля 2020

Как насчет некоторой комбинации форматирования (ниже с f-строкой, но можно сделать иначе) и нарезки:

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 символами группы.

* 103 3 * В приведенном выше решении 8 несколько жестко закодировано. Если вы хотите, чтобы это был параметр, вы можете посмотреть (возможно, вариацию) @ kederra c первый ответ , используя int.from_bytes(). Это может выглядеть примерно так:
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
2 голосов
/ 08 апреля 2020

вы можете использовать:

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 метод может принимать двоичный формат spe c.


также вы можете использовать более компактную форму, где каждый байт отформатирован:

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)
0 голосов
/ 10 апреля 2020

Это ленивая версия.
Она не загружает и не обрабатывает все байты.
Эта команда останавливает независимо от размера ввода.
Другие решения могут не быть!

Я использую 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))))
    )

Test;

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"
0 голосов
/ 06 апреля 2020
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 <= 0: return ...: если мы достигли целевой длины, остановитесь и вернитесь.
  • ''.join(output_chars): создайте строку из списка.
  • str(byte >> 7 & 1)
    • byte >> 7: Shift 7 биты вправо (остается только MSB, поскольку байт имеет 8 бит.)
    • MSB означает старший бит
    • (...) & 1: побитовая и операция. Извлекает LSB.
  • byte <<= 1: сдвиг на 1 бит влево для byte.
  • length -= 1: уменьшение length.
0 голосов
/ 08 марта 2020

Это работает в 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

0 голосов
/ 07 марта 2020

Это дает ответ без предопределенного двоичного представления python 0b:

bit_str = ' '.join(bin(i).replace('0b', '') for i in bytes_val)
0 голосов
/ 07 марта 2020

когда вы говорите BIT, вы имеете в виду двоичный файл? Я бы попробовал

bytes_val = b'\\x80\\x00'

for byte in bytes_val:
    value_in_binary = bin(byte)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...