Saltar al contenido

Iterar sobre bytes individuales en Python 3

Hola usuario de nuestro sitio, descubrimos la respuesta a lo que andabas buscando, has scroll y la hallarás aquí.

Solución:

Si le preocupa el rendimiento de este código y un int como un byte no es una interfaz adecuada en su caso, entonces probablemente debería reconsiderar las estructuras de datos que usa, por ejemplo, use str objetos en su lugar.

Podrías cortar el bytes objeto para obtener 1 longitud bytes objetos:

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

Hay PEP 0467: mejoras menores de API para secuencias binarias que propone bytes.iterbytes() método:

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

int.to_bytes

int los objetos tienen un método to_bytes que se puede usar para convertir un int en su byte correspondiente:

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

Al igual que con otras respuestas, no está claro que esto sea más legible que la solución original del OP: creo que los argumentos de longitud y orden de bytes lo hacen más ruidoso.

estructura.desempaquetar

Otro enfoque sería usar struct.unpack, aunque esto también podría considerarse difícil de leer, a menos que esté familiarizado con el módulo struct:

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

(Como jfs observa en los comentarios, el formato string por struct.unpack se puede construir dinámicamente; en este caso, sabemos que la cantidad de bytes individuales en el resultado debe ser igual a la cantidad de bytes en la cadena de bytes original, por lo que struct.unpack(str(len(bytestring)) + 'c', bytestring) es posible.)

Rendimiento

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

>>> # OP's 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's answer
>>> timeit.timeit(setup="from __main__ import bs", 
                  stmt="list(map(bytes, zip(bs)))")
27.476876026019454

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

>>> # user38's answer (with Leon's 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 parece ser al menos un orden de magnitud más rápido que otros métodos, presumiblemente porque opera a nivel de byte. int.to_bytespor otro lado, funciona peor que la mayoría de los enfoques “obvios”.

Pensé que podría ser útil comparar los tiempos de ejecución de los diferentes enfoques, así que hice un punto de referencia (usando mi biblioteca simple_benchmark):

ingrese la descripción de la imagen aquí

Probablemente, como era de esperar, la solución NumPy es, con mucho, la solución más rápida para objetos de bytes grandes.

Pero si se desea una lista resultante, entonces tanto la solución NumPy (con el tolist()) y el struct solución son mucho más rápidos que las otras alternativas.

No incluí la respuesta de guettlis porque es casi idéntica a la solución jfs, solo que en lugar de una comprensión, se usa una función de generador.

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()

Recuerda comunicar este artículo si si solucionó tu problema.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *