Подтвердить что ты не робот

Перебирать отдельные байты в Python 3

При выполнении итерации объекта bytes в Python 3 пользователь получает bytes как ints:

>>> [b for b in b'123']
[49, 50, 51]

Как получить объекты с длиной 1 bytes вместо этого?

Возможно следующее, но не очень очевидное для читателя и, скорее всего, плохое:

>>> [bytes([b]) for b in b'123']
[b'1', b'2', b'3']
4b9b3361

Ответ 1

Если вас беспокоит производительность этого кода, а int в качестве байта - не подходящий интерфейс в вашем случае, то вам, вероятно, следует пересмотреть структуры данных, которые вы используете, например, используйте str объекты.

Вы можете обрезать объект bytes для получения объектов длиной bytes длиной <1 > :

L = [bytes_obj[i:i+1] for i in range(len(bytes_obj))]

Существует PEP 0467 - Небольшие усовершенствования API для двоичных последовательностей, которые предлагают метод bytes.iterbytes():

>>> list(b'123'.iterbytes())
[b'1', b'2', b'3']

Ответ 2

int.to_bytes

int объекты имеют метод to_bytes, который можно использовать для преобразования целого числа в соответствующий ему байт:

>>> import sys
>>> [i.to_bytes(1, sys.byteorder) for i in b'123']
[b'1', b'2', b'3']

Как и с некоторыми другими ответами, неясно, что это более читабельно, чем оригинальное решение OP: я думаю, длина и аргументы метеоролога делают его более шумным.

struct.unpack

Другой подход заключается в использовании struct.unpack, хотя это также может считаться трудным для чтения, если вы не знакомы с модулем struct:

>>> import struct
>>> struct.unpack('3c', b'123')
(b'1', b'2', b'3')

(Как отмечает jfs в комментариях, строку формата для struct.unpack можно построить динамически; в этом случае мы знаем, что число отдельных байтов в результате должно равняться количеству байтов в исходной строке байтов, поэтому struct.unpack(str(len(bytestring)) + 'c', bytestring) возможно.)

Производительность

>>> import random, timeit
>>> bs = bytes(random.randint(0, 255) for i in range(100))

>>> # OP solution
>>> timeit.timeit(setup="from __main__ import bs",
                  stmt="[bytes([b]) for b in bs]")
46.49886950897053

>>> # Accepted answer from jfs
>>> timeit.timeit(setup="from __main__ import bs",
                  stmt="[bs[i:i+1] for i in range(len(bs))]")
20.91463226894848

>>>  # Leon answer
>>> timeit.timeit(setup="from __main__ import bs", 
                  stmt="list(map(bytes, zip(bs)))")
27.476876026019454

>>> # guettli answer
>>> timeit.timeit(setup="from __main__ import iter_bytes, bs",        
                  stmt="list(iter_bytes(bs))")
24.107485140906647

>>> # user38 answer (with Leon suggested fix)
>>> timeit.timeit(setup="from __main__ import bs", 
                  stmt="[chr(i).encode('latin-1') for i in bs]")
45.937552741961554

>>> # Using int.to_bytes
>>> timeit.timeit(setup="from __main__ import bs;from sys import byteorder", 
                  stmt="[x.to_bytes(1, byteorder) for x in bs]")
32.197659170022234

>>> # Using struct.unpack, converting the resulting tuple to list
>>> # to be fair to other methods
>>> timeit.timeit(setup="from __main__ import bs;from struct import unpack", 
                  stmt="list(unpack('100c', bs))")
1.902243083808571

struct.unpack кажется, по крайней мере, на порядок быстрее, чем другие методы, предположительно потому, что он работает на уровне байтов. int.to_bytes, с другой стороны, работает хуже, чем большинство "очевидных" подходов.

Ответ 3

начиная с python 3.5 вы можете использовать форматирование в байтах и байтовых массивах:

[b'%c' % i for i in b'123']

вывод:

[b'1', b'2', b'3']

вышеупомянутое решение в 2-3 раза быстрее вашего первоначального подхода, если вы хотите более быстрое решение, я предлагаю использовать numpy.frombuffer:

import numpy as np
np.frombuffer(b'123', dtype='S1')

выход:

array([b'1', b'2', b'3'], 
      dtype='|S1')

Второе решение на ~ 10% быстрее, чем struct.unpack (я использовал тот же тест производительности, что и @snakecharmerb, против 100 случайных байтов)

Ответ 4

Я подумал, что было бы полезно сравнить время выполнения разных подходов, поэтому я сделал тест (используя мою библиотеку simple_benchmark):

enter image description here

Возможно, неудивительно, что решение NumPy, безусловно, является самым быстрым решением для объекта с большими байтами.

Но если нужен результирующий список, то и решение NumPy (с tolist()), и решение struct намного быстрее, чем другие альтернативы.

Я не включил ответ геттлиса, потому что он почти идентичен решению jfs, просто вместо понимания используется генераторная функция.

import numpy as np
import struct
import sys

from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()

@b.add_function()
def jfs(bytes_obj):
    return [bytes_obj[i:i+1] for i in range(len(bytes_obj))]

@b.add_function()
def snakecharmerb_tobytes(bytes_obj):
    return [i.to_bytes(1, sys.byteorder) for i in bytes_obj]

@b.add_function()
def snakecharmerb_struct(bytes_obj):
    return struct.unpack(str(len(bytes_obj)) + 'c', bytes_obj)

@b.add_function()
def Leon(bytes_obj):
    return list(map(bytes, zip(bytes_obj)))

@b.add_function()
def rusu_ro1_format(bytes_obj):
    return [b'%c' % i for i in bytes_obj]

@b.add_function()
def rusu_ro1_numpy(bytes_obj):
    return np.frombuffer(bytes_obj, dtype='S1')

@b.add_function()
def rusu_ro1_numpy_tolist(bytes_obj):
    return np.frombuffer(bytes_obj, dtype='S1').tolist()

@b.add_function()
def User38(bytes_obj):
    return [chr(i).encode() for i in bytes_obj]

@b.add_arguments('byte object length')
def argument_provider():
    for exp in range(2, 18):
        size = 2**exp
        yield size, b'a' * size

r = b.run()
r.plot()

Ответ 5

Трио из map(), bytes() и zip() делает свое дело:

>>> list(map(bytes, zip(b'123')))
[b'1', b'2', b'3']

Однако я не думаю, что он лучше читается, чем [bytes([b]) for b in b'123'], или работает лучше.

Ответ 6

Я использую этот вспомогательный метод:

def iter_bytes(my_bytes):
    for i in range(len(my_bytes)):
        yield my_bytes[i:i+1]

Работает для Python2 и Python3.

Ответ 7

Интересно, будет ли объект массива лучше соответствовать вашим целям и избежать ненужных преобразований.

Ответ 8

Короткий способ сделать это:

[chr(i).encode() for i in b'123']