Перебирать отдельные байты в 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']
Ответы
Ответ 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']