¿Por qué “bytes (n)” crea una cadena de n bytes de longitud en lugar de convertir n en una representación binaria?

11 minutos de lectura

avatar de usuario de astrojuanlu
astrojuanlu

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 sobre cómo agregar format a bytes (ver también formato de bytes de Python 3):

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) devolviera la ASCIIficación de ese int; pero, sinceramente, incluso un error sería mejor que este comportamiento. (Si quisiera este comportamiento, que nunca tuve, preferiría que fuera un método de clase, invocado como “bytes.zeroes(n)”.)

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

  • 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

    5 de abril de 2017 a las 6:56


  • Qué hay de malo en: ("3" + "\r\n").encode()?

    – GLRoman

    27 de agosto de 2020 a las 16:34

avatar de usuario de brunsgaard
brunsgaard

Desde python 3.2 puedes usar to_bytes:

>>> (1024).to_bytes(2, byteorder="big")
b'\x04\x00'
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')

Respectivamente, x == int_from_bytes(int_to_bytes(x)). Tenga en cuenta que la codificación anterior solo funciona para enteros sin signo (no negativos).

Para enteros con signo, la longitud en bits es un poco más complicada de calcular:

def int_to_bytes(number: int) -> bytes:
    return number.to_bytes(length=(8 + (number + (number < 0)).bit_length()) // 8, byteorder="big", signed=True)

def int_from_bytes(binary_data: bytes) -> Optional[int]:
    return int.from_bytes(binary_data, byteorder="big", signed=True)

  • Si bien esta respuesta es buena, solo funciona para enteros sin signo (no negativos). Lo he adaptado, escribe una respuesta que también funciona para enteros con signo.

    – Asclepio

    11 de enero de 2019 a las 6:32

  • Eso no ayuda a conseguir b"3" desde 3, como dice la pregunta. (Dará b"\x03".)

    – gsnedders

    22 de mayo de 2019 a las 14:29

  • Podría valer la pena señalar que ambos to_bytes y from_bytes apoyar un signed argumento. Esto permite almacenar números tanto positivos como negativos, al costo de un bit adicional.

    – Señor Miyagi

    20 de agosto de 2020 a las 8:25

  • (stackoverflow.com/a/64502258/5267751 explica lo que +7 es para.)

    – usuario202729

    8 de febrero de 2021 a las 14:25

  • ¿Por qué son necesarios los paréntesis y dónde puedo encontrar documentación sobre ellos?

    – young_souvlaki

    6 abr 2021 a las 19:01

Avatar de usuario de Tim Pietzcker
Tim Pietzcker

Esa es la forma en que se diseñó, y tiene sentido porque, por lo general, llamarías bytes en un iterable en lugar de un solo entero:

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

Él los documentos indican estoasí como la cadena de documentación para bytes:

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

  • Tenga en cuenta que lo anterior solo funciona con python 3. En python 2 bytes es solo un alias para strlo que significa bytes([3]) te dio '[3]'.

    – botchniaque

    17 de agosto de 2016 a las 13:48

  • En Python 3, tenga en cuenta que bytes([n]) solo funciona para int n de 0 a 255. Para cualquier otra cosa, plantea ValueError.

    – Asclepio

    21 de diciembre de 2016 a las 6:29

  • @ABB: No es realmente sorprendente ya que un byte solo puede almacenar valores entre 0 y 255.

    –Tim Pietzcker

    21 de diciembre de 2016 a las 7:15

  • También hay que señalar que bytes([3]) sigue siendo 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])cual es b'3'no b'\x03'.

    – Lenz

    01/04/2017 a las 21:13

  • bytes(500) crea una cadena de bytes con len == 500. No crea una cadena de bytes que codifica el número entero 500. Y estoy de acuerdo en que bytes([500]) no puede funcionar, por lo que esa también es una respuesta incorrecta. Probablemente la respuesta correcta es int.to_bytes() para versiones >= 3.1.

    – weberc2

    20 de junio de 2019 a las 21:57


Puedes usar el paquete de estructura:

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

El “>” es el orden de bytes (big-endian) y el “yo” es el carácter de formato. Así que puedes ser específico si quieres 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 pitón 3.

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

  • @AndyHayden Para aclarar, dado que una estructura tiene un tamaño estándar independientemente de la entrada, I, Hy B trabajar hasta 2**k - 1 donde k es 32, 16 y 8 respectivamente. Para entradas más grandes plantean struct.error.

    – Asclepio

    21 de diciembre de 2016 a las 13:45

  • Presumiblemente votado negativamente ya que no responde a 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” y no el carácter ASCII “\x03”

    – Dave Jones

    5 de marzo de 2017 a las 21:17

  • @DaveJones ¿Qué te hace pensar que eso es lo que quiere el OP? Él respuesta aceptada devoluciones \x03y 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

    5 de marzo de 2017 a las 23:32


  • @DaveJones Además, la razón por la que agregué esta respuesta fue porque Google lo lleva aquí cuando busca hacer precisamente esto. Así que por eso está aquí.

    –Andy Hayden

    5 de marzo de 2017 a las 23:36

  • Esto no solo funciona igual en 2 y 3, sino que es más rápido que ambos bytes([x]) y (x).to_bytes() métodos en Python 3.5. Eso fue inesperado.

    – Mark Ransom

    7 de marzo de 2017 a las 17:03

avatar de usuario de jfs
jfs

Python 3.5+ introduce %-interpolación (printf-estilo de formato) para bytes:

>>> b'%d\r\n' % 3
b'3\r\n'

Ver PEP 0461 — Agregar % de formato a bytes y bytearray.

En versiones anteriores, podría usar str y .encode('ascii') el resultado:

>>> s="%d\r\n" % 3
>>> s.encode('ascii')
b'3\r\n'

Nota: Es diferente de lo que int.to_bytes produce:

>>> n = 3
>>> n.to_bytes((n.bit_length() + 7) // 8, 'big') or b'\0'
b'\x03'
>>> b'3' == b'\x33' != b'\x03'
True

Avatar de usuario de Schcriher
Schcriher

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 vía 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

  • terminé haciendo bytes(str(n), 'ascii') + b'\r\n' o str(n).encode('ascii') + b'\r\n'. ¡Gracias! 🙂

    – astrojuanlu

    9 de enero de 2014 a las 14:32

  • @Juanlu001, también "{}\r\n".format(n).encode() No creo que haya ningún daño al usar la codificación utf8 predeterminada

    – John LaRooy

    12 de febrero de 2015 a las 0:33

La ASCIIficación de 3 es "\x33" no "\x03"!

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

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

La inicialización de bytes con una longitud dada tiene sentido y es la más útil, ya que a menudo se usan para crear algún tipo de búfer para el que necesita alguna memoria asignada de un tamaño determinado. A menudo uso esto cuando inicializo matrices o expando algún archivo escribiéndole ceros.

  • terminé haciendo bytes(str(n), 'ascii') + b'\r\n' o str(n).encode('ascii') + b'\r\n'. ¡Gracias! 🙂

    – astrojuanlu

    9 de enero de 2014 a las 14:32

  • @Juanlu001, también "{}\r\n".format(n).encode() No creo que haya ningún daño al usar la codificación utf8 predeterminada

    – John LaRooy

    12 de febrero de 2015 a las 0:33

avatar de usuario de mkrieger1
mkrieger1

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.

Basado en los tiempos a continuación, y en la tendencia general que observé al probar muchos valores y configuraciones diferentes, struct.pack parece ser el más rápido, seguido de int.to_bytes, bytesy con str.encode (como era de esperar) siendo el más lento. Tenga en cuenta que los resultados muestran algo más de variación de lo que se representa, y int.to_bytes y bytes a veces cambió la clasificación de velocidad durante las pruebas, pero struct.pack es 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 (llamado 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)'"
        ))

  • @ABB Como 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 medidas no eran lo suficientemente generales para adaptarse a la mayoría de las situaciones. ¿O mi metodología de medición era deficiente? Si es lo último, me interesaría escuchar 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 (quizás de nicho), solo estoy tratando con enteros en el rango [0, 255], y esa es la audiencia a la que pretendía dirigirme con esta respuesta. ¿Mi respuesta no fue clara? Puedo editarlo para mayor claridad…

    –Graham

    11 de enero de 2019 a las 12:19


  • ¿Qué pasa con la técnica de simplemente indexar una codificación precalculada para el rango? El cálculo previo no estaría sujeto a tiempos, solo la indexación lo estaría.

    – Asclepio

    11 de enero de 2019 a las 15:29


  • @ABB Esa es una buena idea. Eso suena como 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

    11 de enero de 2019 a las 19:03

  • Si realmente desea cronometrar los bytes desde iterables, debe usar bytes((i,)) en vez de bytes([i]) porque las listas son más complejas, usan más memoria y tardan más en inicializarse. En este caso, para nada.

    – Bachsau

    28 de marzo de 2019 a las 6:28

¿Ha sido útil esta solución?