Conversione da int a byte in Python 3


178

Stavo cercando di creare questo oggetto byte in Python 3:

b'3\r\n'

così ho provato l'ovvio (per me) e ho trovato un comportamento strano:

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

Apparentemente:

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

Non sono stato in grado di vedere alcun puntatore sul perché la conversione dei byte funziona in questo modo leggendo la documentazione. Tuttavia, ho trovato alcuni messaggi a sorpresa in questo problema di Python sull'aggiunta formatai byte (vedi anche la formattazione dei byte di Python 3 ):

http://bugs.python.org/issue3982

Questo interagisce ancora più male con stranezze come byte (int) che restituiscono zero adesso

e:

Sarebbe molto più conveniente per me se bytes (int) restituisse la ASCIIfication di quell'int; ma onestamente, anche un errore sarebbe meglio di questo comportamento. (Se volessi questo comportamento - che non ho mai avuto - preferirei che fosse un metodo di classe, invocato come "bytes.zeroes (n)".)

Qualcuno può spiegarmi da dove viene questo comportamento?


1
correlati al titolo:3 .to_bytes
jfs

2
Non è chiaro dalla domanda se si desidera il valore intero 3 o il valore del carattere ASCII che rappresenta il numero tre (valore intero 51). Il primo è byte ([3]) == b '\ x03'. Quest'ultimo è byte ([ord ('3')]) == b'3 '.
florisla,

Risposte:


177

Questo è il modo in cui è stato progettato - e ha senso perché di solito, si chiamerebbe bytesun iterabile anziché un singolo intero:

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

I documenti lo affermano , così come la documentazione per bytes:

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

25
Attenzione che quanto sopra funziona solo con Python 3. In Python 2 bytesè solo un alias per str, il che significa che bytes([3])ti dà '[3]'.
botchniaque,

8
In Python 3, nota che bytes([n])funziona solo per int n da 0 a 255. Per qualsiasi altra cosa si alza ValueError.
Acumenus,

8
@ABB: Non è sorprendente perché un byte può memorizzare solo valori compresi tra 0 e 255.
Tim Pietzcker,

7
Va anche notato che bytes([3])è ancora diverso da ciò che l'OP voleva - vale a dire il valore di byte utilizzato per codificare la cifra "3" in ASCII, vale a dire. bytes([51]), che è b'3', no b'\x03'.
lenz,

2
bytes(500)crea un bytestring w / len == 500. Non crea un bytestring che codifica il numero intero 500. E concordo sul fatto che bytes([500])non può funzionare, motivo per cui anche questa è la risposta sbagliata. Probabilmente la risposta giusta è int.to_bytes()per le versioni> = 3.1.
weberc2,

199

Da python 3.2 puoi farlo

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

Di conseguenza, x == int_from_bytes(int_to_bytes(x)). Si noti che questa codifica funziona solo per numeri interi senza segno (non negativi).


4
Mentre questa risposta è buona, funziona solo per numeri interi senza segno (non negativi). L'ho adattato per scrivere una risposta che funziona anche per numeri interi con segno.
Acumenus,

1
Ciò non aiuta a ottenere b"3"da 3, come pone la domanda. ( b"\x03"
Darà

41

Puoi usare il pacchetto di struct :

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

">" È l' ordine dei byte (big-endian) e "I" è il carattere del formato . Quindi puoi essere specifico se vuoi fare qualcos'altro:

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

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

Funziona allo stesso modo sia su Python 2 che su Python 3 .

Nota: l'operazione inversa (byte in int) può essere eseguita con unpack .


2
@AndyHayden Per chiarire, poiché una struttura ha una dimensione standard indipendentemente dall'ingresso, I, H, e Blavorare fino 2**k - 1dove k è 32, 16 e 8, rispettivamente. Per input più grandi aumentano struct.error.
Acumenus,

Presumibilmente sottovalutato perché non risponde alla domanda: l'OP vuole sapere come generare b'3\r\n', cioè una stringa di byte contenente il carattere ASCII "3" non il carattere ASCII "\ x03"
Dave Jones

1
@DaveJones Cosa ti fa pensare che sia quello che vuole l'OP? La risposta accettata ritorna \x03e la soluzione se vuoi solo b'3'è banale. Il motivo citato da ABB è molto più plausibile ... o almeno comprensibile.
Andy Hayden,

@DaveJones Inoltre, il motivo per cui ho aggiunto questa risposta è perché Google ti porta qui durante la ricerca per fare esattamente questo. Ecco perché è qui.
Andy Hayden,

5
Non solo funziona allo stesso modo in 2 e 3, ma è più veloce di entrambi i metodi bytes([x])e (x).to_bytes()in Python 3.5. È stato inaspettato.
Mark Ransom


11

La documentazione dice:

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

La sequenza:

b'3\r\n'

È il carattere '3' (decimale 51) il carattere '\ r' (13) e '\ n' (10).

Pertanto, il modo in cui lo tratterà come tale, ad esempio:

>>> 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'

Testato su IPython 1.1.0 e Python 3.2.3


1
Ho finito per fare bytes(str(n), 'ascii') + b'\r\n'o str(n).encode('ascii') + b'\r\n'. Grazie! :)
astrojuanlu,

1
@ Juanlu001, inoltre "{}\r\n".format(n).encode(), non credo che ci sia alcun danno fatto usando la codifica utf8 predefinita
John La Rooy,

6

La ASCIIfication di 3 "\x33"non lo è "\x03"!

Questo è ciò che fa Python, str(3)ma sarebbe totalmente sbagliato per i byte, poiché dovrebbero essere considerati array di dati binari e non essere abusati come stringhe.

Il modo più semplice per ottenere ciò che vuoi è bytes((3,)), che è meglio che bytes([3])inizializzare un elenco è molto più costoso, quindi non usare mai gli elenchi quando puoi usare le tuple. Puoi convertire numeri interi più grandi usando int.to_bytes(3, "little").

L'inizializzazione di byte con una determinata lunghezza ha senso ed è la più utile, in quanto vengono spesso utilizzati per creare un tipo di buffer per il quale è necessario un po 'di memoria di dimensioni assegnate. Lo uso spesso durante l'inizializzazione di array o l'espansione di alcuni file scrivendo zero su di esso.


1
Esistono diversi problemi con questa risposta: (a) La notazione di escape di b'3'è b'\x33', no b'\x32'. (b) (3)non è una tupla: devi aggiungere una virgola. (c) Lo scenario di inizializzazione di una sequenza con zero non si applica agli bytesoggetti, poiché sono immutabili (ha senso per bytearrays, però).
lenz,

Grazie per il tuo commento. Ho corretto quei due ovvi errori. In caso di bytese bytearray, penso che sia principalmente una questione di coerenza. Ma è anche utile se si desidera inserire alcuni zeri in un buffer o file, nel qual caso viene utilizzato solo come origine dati.
Bachsau,

5

int(incluso Python2 long) può essere convertito bytesutilizzando la seguente funzione:

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 conversione inversa può essere eseguita da un'altra:

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)

Entrambe le funzioni funzionano su Python2 e Python3.


'hex_value ='% x '% i' non funzionerà con Python 3.4. Ottieni un TypeError, quindi dovresti usare hex () invece.
bjmc,

@bjmc sostituito con str.format. Questo dovrebbe funzionare su Python 2.6+.
renskiy,

Grazie, @renskiy. Si potrebbe desiderare di utilizzare 'hex_codec' invece di 'hex' perché sembra 'hex' alias non è disponibile su tutti Python 3 Uscite di stackoverflow.com/a/12917604/845210
bjmc

@bjmc fixed. Grazie
renskiy il

Questo fallisce sugli interi negativi su Python 3.6
Berserker,

4

Ero curioso delle prestazioni di vari metodi per un singolo int nella gamma [0, 255] , quindi ho deciso di fare alcuni test di temporizzazione.

Sulla base dei tempi indicati, e dalla tendenza generale ho osservato da provare molti valori e configurazioni diverse, struct.packsembra essere il più veloce, seguito da int.to_bytes, bytese con str.encode(non sorprende) è la più lenta. Si noti che i risultati mostrano alcune variazioni in più rispetto a quanto rappresentato, int.to_bytese bytestalvolta commutano la classifica della velocità durante i test, mastruct.pack è chiaramente il più veloce.

Risultati in CPython 3.7 su 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

Modulo di test (denominato 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)'"
        ))

1
@ABB Come menzionato nella mia prima frase, lo sto misurando solo per un singolo int nell'intervallo [0, 255]. Presumo che per "indicatore sbagliato" intendi che le mie misurazioni non erano abbastanza generali per adattarsi alla maggior parte delle situazioni? O la mia metodologia di misurazione era scarsa? In quest'ultimo caso, sarei interessato a sapere cosa hai da dire, ma se il primo, non avrei mai sostenuto che le mie misurazioni fossero generiche per tutti i casi d'uso. Per la mia (forse di nicchia) situazione, ho a che fare solo con ints nella gamma [0, 255], e questo è il pubblico che intendevo affrontare con questa risposta. La mia risposta non era chiara? Posso modificarlo per chiarezza ...
Graham,

1
Che dire della tecnica di indicizzazione di una codifica pre-calcolata per l'intervallo? Il pre-calcolo non sarebbe soggetto a tempismo, solo l'indicizzazione lo sarebbe.
Acumenus,

@ABB Questa è una buona idea. Sembra che sarà più veloce di ogni altra cosa. Farò un po 'di tempo e lo aggiungerò a questa risposta quando avrò del tempo.
Graham,

3
Se vuoi veramente cronometrare la cosa bytes-it-iterable, dovresti usare bytes((i,))invece che bytes([i])perché l'elenco è più complesso, usa più memoria e impiega molto tempo per inizializzare. In questo caso, per niente.
Bachsau,

4

Sebbene la risposta precedente di brunsgaard sia una codifica efficiente, funziona solo per numeri interi senza segno. Questo si basa su di esso per funzionare con numeri interi sia firmati che non firmati.

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)

Per l'encoder, (i + ((i * signed) < 0)).bit_length()viene utilizzato anziché solo i.bit_length()perché quest'ultimo porta a una codifica inefficiente di -128, -32768, ecc.


Ringraziamento: CervEd per aver risolto un'inefficienza minore.


int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)èFalse
CervEd,

Non stai usando la lunghezza 2, stai calcolando la lunghezza in bit dell'intero con segno, aggiungendo 7 e quindi 1, se è un intero con segno. Alla fine lo converti nella lunghezza in byte. Ciò produce risultati inaspettati per -128, -32768ecc.
CervEd


Ecco come risolverlo(i+(signed*i<0)).bit_length()
CervEd

3

Il comportamento deriva dal fatto che in Python prima della versione 3 bytesera solo un alias per str. In Python3.x bytesè una versione immutabile di bytearray- tipo completamente nuovo, non retrocompatibile.


3

Da byte documenti :

Di conseguenza, gli argomenti del costruttore vengono interpretati come per bytearray ().

Quindi, dai documenti bytearray :

Il parametro sorgente opzionale può essere utilizzato per inizializzare l'array in diversi modi:

  • Se è un numero intero, l'array avrà quella dimensione e verrà inizializzato con byte null.

Si noti che differisce dal comportamento 2.x (dove x> = 6), dove bytesè semplicemente str:

>>> bytes is str
True

PEP 3112 :

La stringa 2.6 differisce dal tipo di byte 3.0 in vari modi; in particolare, il costruttore è completamente diverso.


0

Alcune risposte non funzionano con numeri grandi.

Convertire un numero intero nella rappresentazione esadecimale, quindi convertirlo in byte:

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

Risultato:

>>> 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'

1
"Tutti gli altri metodi non funzionano con grandi numeri." Questo non è vero, int.to_bytesfunziona con qualsiasi numero intero.
juanpa.arrivillaga,

@ juanpa.arrivillaga sì, mia cattiva. Ho modificato la mia risposta.
Max Malysh,

-1

Se la domanda è come convertire un intero stesso (non equivalente in stringa) in byte, penso che la risposta affidabile sia:

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

Maggiori informazioni su questi metodi qui:

  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

1
In che modo differisce dalla risposta di Brunsgaard, pubblicata 5 anni fa e attualmente la risposta più votata?
Arthur Tacca,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.