Solución:
Aquí hay un ejemplo de cómo hacerlo de la primera forma que mencionó Patrick: convierta la cadena de bits en un int y tome 8 bits a la vez. La forma natural de hacerlo genera los bytes en orden inverso. Para volver a poner los bytes en el orden correcto, utilizo la notación de rebanada extendida en el bytearray con un paso de -1: b[::-1]
.
def bitstring_to_bytes(s):
v = int(s, 2)
b = bytearray()
while v:
b.append(v & 0xff)
v >>= 8
return bytes(b[::-1])
s = "0110100001101001"
print(bitstring_to_bytes(s))
Claramente, la segunda vía de Patrick es más compacta. 🙂
Sin embargo, hay una mejor manera de hacer esto en Python 3: use el método int.to_bytes:
def bitstring_to_bytes(s):
return int(s, 2).to_bytes((len(s) + 7) // 8, byteorder="big")
Si len(s)
es garantizado para ser un múltiplo de 8, entonces el primer argumento de .to_bytes
se puede simplificar:
return int(s, 2).to_bytes(len(s) // 8, byteorder="big")
Esto elevará OverflowError
si len(s)
es no un múltiplo de 8, que puede ser deseable en algunas circunstancias.
Otra opción es utilizar la doble negación para realizar la división del techo. Para enteros a y b, división de piso usando //
n = a // b
da el entero n tal que
n <= a / b
47 // 10
da 4, y
-47 // 10
da -5. Entonces
-(-47 // 10)
da 5, realizando eficazmente la división del techo.
Así en bitstring_to_bytes
nosotros podría hacer:
return int(s, 2).to_bytes(-(-len(s) // 8), byteorder="big")
Sin embargo, no muchas personas están familiarizadas con este lenguaje eficiente y compacto, por lo que generalmente se considera menos legible que
return (s, 2).to_bytes((len(s) + 7) // 8, byteorder="big")
Tiene que convertirlo en un int y tomar 8 bits a la vez, o cortarlo en cadenas de 8 bytes de largo y luego convertir cada uno de ellos en ints. En Python 3, como muestran las respuestas de PM 2Ring y JF Sebastian, el to_bytes()
método de int
le permite hacer el primer método de manera muy eficiente. Esto no está disponible en Python 2, por lo que para las personas que se quedan con eso, el segundo método puede ser más eficiente. Aquí hay un ejemplo:
>>> s = "0110100001101001"
>>> bytes(int(s[i : i + 8], 2) for i in range(0, len(s), 8))
b'hi'
Para desglosar esto, la declaración de rango comienza en el índice 0 y nos da índices en la cadena de origen, pero avanza 8 índices a la vez. Ya que s
tiene 16 caracteres, nos dará dos índices:
>>> list(range(0, 50, 8))
[0, 8, 16, 24, 32, 40, 48]
>>> list(range(0, len(s), 8))
[0, 8]
(Usamos list()
aquí para mostrar los valores que se recuperarán del iterador de rango en Python 3.)
Luego podemos construir sobre esto para dividir la cadena tomando partes de 8 caracteres de largo:
>>> [s[i : i + 8] for i in range(0, len(s), 8)]
['01101000', '01101001']
Luego, podemos convertir cada uno de esos en números enteros, base 2:
>>> list(int(s[i : i + 8], 2) for i in range(0, len(s), 8))
[104, 105]
Y finalmente, envolvemos todo en bytes()
para obtener la respuesta:
>>> bytes(int(s[i : i + 8], 2) for i in range(0, len(s), 8))
b'hi'
>>> zero_one_string = "0110100001101001"
>>> int(zero_one_string, 2).to_bytes((len(zero_one_string) + 7) // 8, 'big')
b'hi'
Vuelve bytes
objeto que es una secuencia inmutable de bytes. Si quieres conseguir un bytearray
– una secuencia mutable de bytes – luego simplemente llame bytearray(b'hi')
.