Convertir int a bytes en Python 3

178

Estaba tratando de construir este objeto de bytes en Python 3:

b'3\r\n'

así que probé lo obvio (para mí) y encontré un comportamiento extraño:

>>> bytes(3) + b'\r\n'
b'\x00\x00\x00\r\n'

Aparentemente:

>>> bytes(10)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

No he podido ver ningún indicador sobre por qué la conversión de bytes funciona de esta manera al leer la documentación. Sin embargo, encontré algunos mensajes sorpresa en este problema de Python acerca de agregar formatbytes (ver también el formato Python 3 bytes ):

http://bugs.python.org/issue3982

Esto interactúa aún peor con rarezas como bytes (int) que devuelven ceros ahora

y:

Sería mucho más conveniente para mí si bytes (int) devuelve la ASCIIFICACIÓN de ese int; pero sinceramente, incluso un error sería mejor que este comportamiento. (Si quisiera este comportamiento, que nunca he tenido, prefiero que sea un método de clase, invocado como "bytes.zeroes (n)").

¿Alguien puede explicarme de dónde viene este comportamiento?

astrojuanlu
fuente
1
relacionado con el título:3 .to_bytes
jfs
2
No queda claro a partir de su pregunta si desea el valor entero 3, o el valor del carácter ASCII que representa el número tres (valor entero 51). El primero es bytes ([3]) == b '\ x03'. El último es bytes ([ord ('3')]) == b'3 '.
florisla

Respuestas:

177

Así fue diseñado, y tiene sentido porque, por lo general, llamaría bytesa un entero iterable en lugar de a un único entero:

>>> bytes([3])
b'\x03'

Los documentos indican esto , así como la cadena de documentos para bytes:

 >>> help(bytes)
 ...
 bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Tim Pietzcker
fuente
25
Tenga en cuenta que lo anterior solo funciona con python 3. En python 2 byteses solo un alias para str, lo que significa que bytes([3])le da '[3]'.
botchniaque
8
En Python 3, tenga en cuenta que bytes([n])solo funciona para int n de 0 a 255. Para cualquier otra cosa, aumenta ValueError.
Acumenus
8
@ABB: No es realmente sorprendente ya que un byte solo puede almacenar valores entre 0 y 255.
Tim Pietzcker
77
También se debe tener en cuenta que bytes([3])todavía es diferente de lo que quería el OP, es decir, el valor de byte utilizado para codificar el dígito "3" en ASCII, es decir. bytes([51]), que es b'3', no b'\x03'.
lenz
2
bytes(500)crea una cadena de bytes con len == 500. No crea una cadena de bytes que codifique el entero 500. Y estoy de acuerdo en que bytes([500])no puede funcionar, por lo que esa es la respuesta incorrecta también. Probablemente la respuesta correcta sea int.to_bytes()para versiones> = 3.1.
weberc2
199

Desde python 3.2 puedes hacer

>>> (1024).to_bytes(2, byteorder='big')
b'\x04\x00'

https://docs.python.org/3/library/stdtypes.html#int.to_bytes

def int_to_bytes(x: int) -> bytes:
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')

def int_from_bytes(xbytes: bytes) -> int:
    return int.from_bytes(xbytes, 'big')

En consecuencia x == int_from_bytes(int_to_bytes(x)),. Tenga en cuenta que esta codificación solo funciona para enteros sin signo (no negativos).

brunsgaard
fuente
44
Si bien esta respuesta es buena, solo funciona para enteros sin signo (no negativos). Lo he adaptado escribir una respuesta que también funciona para enteros con signo.
Acumenus
1
Eso no ayuda a conseguir b"3"a partir 3, como la pregunta se refiere. ( b"\x03"
Dará
41

Puede usar el paquete de la estructura :

In [11]: struct.pack(">I", 1)
Out[11]: '\x00\x00\x00\x01'

El ">" es el orden de bytes (big-endian) y el "I" es el carácter de formato . Por lo tanto, puede ser específico si desea hacer otra cosa:

In [12]: struct.pack("<H", 1)
Out[12]: '\x01\x00'

In [13]: struct.pack("B", 1)
Out[13]: '\x01'

Esto funciona igual en python 2 y python 3 .

Nota: la operación inversa (bytes a int) se puede hacer con descomprimir .

Andy Hayden
fuente
2
@AndyHayden Para aclarar, ya que un struct tiene un tamaño estándar independientemente de la entrada, I, H, y Btrabajo hasta 2**k - 1donde k es 32, 16, y 8 respectivamente. Para entradas más grandes aumentan struct.error.
Acumenus
Presumiblemente no se vota porque no responde la pregunta: el OP quiere saber cómo generar b'3\r\n', es decir, una cadena de bytes que contiene el carácter ASCII "3", no el carácter ASCII "\ x03"
Dave Jones
1
@DaveJones ¿Qué te hace pensar que eso es lo que quiere el OP? La respuesta aceptada regresa \x03, y la solución si solo quieres b'3'es trivial. La razón citada por ABB es mucho más plausible ... o al menos comprensible.
Andy Hayden
@DaveJones Además, la razón por la que agregué esta respuesta fue porque Google lo lleva aquí cuando busca hacer precisamente esto. Entonces es por eso que está aquí.
Andy Hayden
55
Esto no solo funciona igual en 2 y 3, sino que es más rápido que los métodos bytes([x])y (x).to_bytes()en Python 3.5. Eso fue inesperado.
Mark Ransom
11

La documentación dice:

bytes(int) -> bytes object of size given by the parameter
              initialized with null bytes

La secuencia:

b'3\r\n'

Es el carácter '3' (51 decimal) el carácter '\ r' (13) y '\ n' (10).

Por lo tanto, la forma lo trataría como tal, por ejemplo:

>>> bytes([51, 13, 10])
b'3\r\n'

>>> bytes('3', 'utf8') + b'\r\n'
b'3\r\n'

>>> n = 3
>>> bytes(str(n), 'ascii') + b'\r\n'
b'3\r\n'

Probado en IPython 1.1.0 y Python 3.2.3

Schcriher
fuente
1
Terminé haciendo bytes(str(n), 'ascii') + b'\r\n'o str(n).encode('ascii') + b'\r\n'. ¡Gracias! :)
astrojuanlu
1
@ Juanlu001, "{}\r\n".format(n).encode()tampoco creo que haya ningún daño al usar la codificación utf8 predeterminada
John La Rooy
6

¡La ASCIIFICACIÓN de 3 "\x33"no lo es "\x03"!

Eso es lo que hace Python, str(3)pero sería totalmente incorrecto para los bytes, ya que deberían considerarse conjuntos de datos binarios y no ser abusados ​​como cadenas.

La forma más fácil de lograr lo que desea es bytes((3,)), que es mejor que bytes([3])porque inicializar una lista es mucho más costoso, así que nunca use listas cuando pueda usar tuplas. Puede convertir enteros más grandes con int.to_bytes(3, "little").

Inicializar bytes con una longitud determinada tiene sentido y es lo más útil, ya que a menudo se usan para crear algún tipo de búfer para el que necesita asignar memoria de un tamaño determinado. A menudo uso esto al inicializar matrices o expandir algún archivo escribiendo ceros en él.

Bachsau
fuente
1
Hay varios problemas con esta respuesta: (a) La notación de escape de b'3'es b'\x33', no b'\x32'. (b) (3)no es una tupla: debe agregar una coma. (c) El escenario de inicializar una secuencia con ceros no se aplica a los bytesobjetos, ya que son inmutables (sin embargo, tiene sentido para bytearrays).
lenz
Gracias por tu comentario. Arreglé esos dos errores obvios. En el caso de bytesy bytearray, creo que es principalmente una cuestión de consistencia. Pero también es útil si desea insertar algunos ceros en un búfer o archivo, en cuyo caso solo se usa como fuente de datos.
Bachsau
5

int(incluyendo Python2 long) se puede convertir bytesusando la siguiente función:

import codecs

def int2bytes(i):
    hex_value = '{0:x}'.format(i)
    # make length of hex_value a multiple of two
    hex_value = '0' * (len(hex_value) % 2) + hex_value
    return codecs.decode(hex_value, 'hex_codec')

La conversión inversa puede ser realizada por otro:

import codecs
import six  # should be installed via 'pip install six'

long = six.integer_types[-1]

def bytes2int(b):
    return long(codecs.encode(b, 'hex_codec'), 16)

Ambas funciones funcionan tanto en Python2 como en Python3.

renskiy
fuente
'hex_value ='% x '% i' no funcionará en Python 3.4. Obtiene un TypeError, por lo que tendría que usar hex () en su lugar.
bjmc
@bjmc reemplazado con str.format. Esto debería funcionar en Python 2.6+.
renskiy
Gracias @renskiy. Es posible que desee usar 'hex_codec' en lugar de 'hex' porque parece que el alias 'hex' no está disponible en todas las versiones de Python 3; consulte stackoverflow.com/a/12917604/845210
bjmc
@bjmc arreglado. Gracias
renskiy
Esto falla en enteros negativos en Python 3.6
Berserker
4

Tenía curiosidad sobre el rendimiento de varios métodos para un solo int en el rango [0, 255], así que decidí hacer algunas pruebas de tiempo.

Sobre la base de los tiempos de abajo, y de la tendencia general he observado de tratar muchos valores y configuraciones diferentes, struct.packque parece ser el más rápido, seguido de int.to_bytes, bytesy con str.encode(como era de esperar) la más lenta. Tenga en cuenta que los resultados muestran algunos más variación que se representa, y int.to_bytesy bytesen ocasiones velocidad con conmutación de rango durante las pruebas, pero struct.packes claramente el más rápido.

Resultados en CPython 3.7 en Windows:

Testing with 63:
bytes_: 100000 loops, best of 5: 3.3 usec per loop
to_bytes: 100000 loops, best of 5: 2.72 usec per loop
struct_pack: 100000 loops, best of 5: 2.32 usec per loop
chr_encode: 50000 loops, best of 5: 3.66 usec per loop

Módulo de prueba (nombrado int_to_byte.py):

"""Functions for converting a single int to a bytes object with that int's value."""

import random
import shlex
import struct
import timeit

def bytes_(i):
    """From Tim Pietzcker's answer:
    https://stackoverflow.com/a/21017834/8117067
    """
    return bytes([i])

def to_bytes(i):
    """From brunsgaard's answer:
    https://stackoverflow.com/a/30375198/8117067
    """
    return i.to_bytes(1, byteorder='big')

def struct_pack(i):
    """From Andy Hayden's answer:
    https://stackoverflow.com/a/26920966/8117067
    """
    return struct.pack('B', i)

# Originally, jfs's answer was considered for testing,
# but the result is not identical to the other methods
# https://stackoverflow.com/a/31761722/8117067

def chr_encode(i):
    """Another method, from Quuxplusone's answer here:
    https://codereview.stackexchange.com/a/210789/140921

    Similar to g10guang's answer:
    https://stackoverflow.com/a/51558790/8117067
    """
    return chr(i).encode('latin1')

converters = [bytes_, to_bytes, struct_pack, chr_encode]

def one_byte_equality_test():
    """Test that results are identical for ints in the range [0, 255]."""
    for i in range(256):
        results = [c(i) for c in converters]
        # Test that all results are equal
        start = results[0]
        if any(start != b for b in results):
            raise ValueError(results)

def timing_tests(value=None):
    """Test each of the functions with a random int."""
    if value is None:
        # random.randint takes more time than int to byte conversion
        # so it can't be a part of the timeit call
        value = random.randint(0, 255)
    print(f'Testing with {value}:')
    for c in converters:
        print(f'{c.__name__}: ', end='')
        # Uses technique borrowed from https://stackoverflow.com/q/19062202/8117067
        timeit.main(args=shlex.split(
            f"-s 'from int_to_byte import {c.__name__}; value = {value}' " +
            f"'{c.__name__}(value)'"
        ))
Graham
fuente
1
@ABB Como se mencionó en mi primera oración, solo estoy midiendo esto para un solo int en el rango [0, 255]. Supongo que por "indicador incorrecto" quiere decir que mis mediciones no fueron lo suficientemente generales como para adaptarse a la mayoría de las situaciones. ¿O fue pobre mi metodología de medición? Si es lo último, me interesaría saber lo que tiene que decir, pero si es lo primero, nunca afirmé que mis medidas fueran genéricas para todos los casos de uso. Para mi situación (tal vez de nicho), solo estoy tratando con entradas en el rango [0, 255], y esa es la audiencia que pretendía abordar con esta respuesta. ¿No estaba clara mi respuesta? Puedo editarlo para mayor claridad ...
Graham
1
¿Qué pasa con la técnica de simplemente indexar una codificación precalculada para el rango? La precomputación no estaría sujeta al tiempo, solo lo estaría la indexación.
Acumenus
@ABB Esa es una buena idea. Parece que será más rápido que cualquier otra cosa. Haré algo de tiempo y lo agregaré a esta respuesta cuando tenga algo de tiempo.
Graham el
3
Si realmente desea cronometrar la cosa de iterar desde bytes, debe usar en bytes((i,))lugar de bytes([i])porque la lista es más compleja, use más memoria y tarde mucho en inicializar. En este caso, por nada.
Bachsau
4

Aunque la respuesta previa de brunsgaard es una codificación eficiente, solo funciona para enteros sin signo. Este se basa en él para funcionar tanto con enteros con signo como sin signo.

def int_to_bytes(i: int, *, signed: bool = False) -> bytes:
    length = ((i + ((i * signed) < 0)).bit_length() + 7 + signed) // 8
    return i.to_bytes(length, byteorder='big', signed=signed)

def bytes_to_int(b: bytes, *, signed: bool = False) -> int:
    return int.from_bytes(b, byteorder='big', signed=signed)

# Test unsigned:
for i in range(1025):
    assert i == bytes_to_int(int_to_bytes(i))

# Test signed:
for i in range(-1024, 1025):
    assert i == bytes_to_int(int_to_bytes(i, signed=True), signed=True)

Para el codificador, (i + ((i * signed) < 0)).bit_length()se usa en lugar de solo i.bit_length()porque este último conduce a una codificación ineficiente de -128, -32768, etc.


Crédito: CervEd por arreglar una ineficiencia menor.

Acumenus
fuente
int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)esFalse
CervEd
No está utilizando la longitud 2, está calculando la longitud de bits del entero con signo, sumando 7 y luego 1, si es un entero con signo. Finalmente lo conviertes a la longitud en bytes. Esto produce resultados inesperados para -128, -32768etc.
CervEd
Continuemos esta discusión en el chat .
CervEd
Así es como lo arreglas(i+(signed*i<0)).bit_length()
CervEd
3

El comportamiento proviene del hecho de que en Python antes de la versión 3 bytesera solo un alias para str. En Python3.x byteshay una versión inmutable de bytearray: tipo completamente nuevo, no compatible con versiones anteriores.

monstruoso
fuente
3

De bytes docs :

En consecuencia, los argumentos del constructor se interpretan como bytearray ().

Luego, de bytearray docs :

El parámetro fuente opcional se puede usar para inicializar la matriz de diferentes maneras:

  • Si es un entero, la matriz tendrá ese tamaño y se inicializará con bytes nulos.

Tenga en cuenta que difiere del comportamiento 2.x (donde x> = 6), donde byteses simplemente str:

>>> bytes is str
True

PEP 3112 :

El 2.6 str difiere del tipo de bytes de 3.0 en varias formas; más notablemente, el constructor es completamente diferente.

alko
fuente
0

Algunas respuestas no funcionan con grandes números.

Convierta un entero a la representación hexadecimal, luego conviértalo a bytes:

def int_to_bytes(number):
    hrepr = hex(number).replace('0x', '')
    if len(hrepr) % 2 == 1:
        hrepr = '0' + hrepr
    return bytes.fromhex(hrepr)

Resultado:

>>> int_to_bytes(2**256 - 1)
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
Max Malysh
fuente
1
"Todos los demás métodos no funcionan con grandes números". Eso no es cierto, int.to_bytesfunciona con cualquier número entero.
juanpa.arrivillaga
@ juanpa.arrivillaga si, mi mal. He editado mi respuesta.
Max Malysh
-1

Si la pregunta es cómo convertir un entero en sí (no su equivalente de cadena) en bytes, creo que la respuesta sólida es:

>>> i = 5
>>> i.to_bytes(2, 'big')
b'\x00\x05'
>>> int.from_bytes(i.to_bytes(2, 'big'), byteorder='big')
5

Más información sobre estos métodos aquí:

  1. https://docs.python.org/3.8/library/stdtypes.html#int.to_bytes
  2. https://docs.python.org/3.8/library/stdtypes.html#int.from_bytes
Nilashish C
fuente
1
¿Cómo es esto diferente de la respuesta de brunsgaard, publicada hace 5 años y actualmente la respuesta más votada?
Arthur Tacca