Convertendo int em bytes no Python 3

177

Eu estava tentando criar esse objeto de bytes no Python 3:

b'3\r\n'

então eu tentei o óbvio (para mim) e encontrei um comportamento estranho:

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

Pelo visto:

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

Não consegui ver nenhum ponteiro sobre por que a conversão de bytes funciona dessa maneira, lendo a documentação. No entanto, encontrei algumas mensagens surpresas neste problema do Python sobre como adicionar formatbytes (consulte também a formatação do Python 3 bytes ):

http://bugs.python.org/issue3982

Isso interage ainda mais mal com esquisitices como bytes (int) retornando zeros agora

e:

Seria muito mais conveniente para mim se bytes (int) retornassem a ASCIIfication desse int; mas, honestamente, até um erro seria melhor que esse comportamento. (Se eu quisesse esse comportamento - o que nunca tive - prefiro que seja um método de classe, invocado como "bytes.zeroes (n)").

Alguém pode me explicar de onde vem esse comportamento?

astrojuanlu
fonte
1
relacionado ao título:3 .to_bytes
jfs
2
Não está claro em sua pergunta se você deseja o valor inteiro 3 ou o valor do caractere ASCII que representa o número três (valor inteiro 51). O primeiro são bytes ([3]) == b '\ x03'. O último é bytes ([ord ('3')]) == b'3 '.
florisla

Respostas:

177

Foi assim que foi projetado - e faz sentido, porque geralmente você poderia chamar bytesum iterável em vez de um único número inteiro:

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

Os documentos afirmam isso , bem como a documentação para bytes:

 >>> help(bytes)
 ...
 bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Tim Pietzcker
fonte
25
Lembre-se de que o acima funciona apenas com o python 3. No python 2 bytesé apenas um apelido para str, o que significa que bytes([3])você fornece '[3]'.
Botchniaque 17/08/16
8
No Python 3, observe que bytes([n])só funciona para int n de 0 a 255. Para qualquer outra coisa, ele gera ValueError.
Acumenus
8
@ABB: Não é realmente surpreendente uma vez que um byte só pode armazenar valores entre 0 e 255.
Tim Pietzcker
7
Deve-se notar também que bytes([3])ainda é diferente do que o OP queria - ou seja, o valor de byte usado para codificar o dígito "3" em ASCII, ou seja. bytes([51]), o que é b'3'não b'\x03'.
Lem
2
bytes(500)cria uma bytestring com len == 500. Ele não cria uma bytestring que codifica o número inteiro 500. E eu concordo que isso bytes([500])não pode funcionar, e é por isso que essa é a resposta errada também. Provavelmente a resposta certa é int.to_bytes()para versões> = 3.1.
weberc2
199

Do python 3.2 você pode fazer

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

Por conseguinte x == int_from_bytes(int_to_bytes(x)),. Observe que essa codificação funciona apenas para números inteiros não assinados (não negativos).

brunsgaard
fonte
4
Embora essa resposta seja boa, ela funciona apenas para números inteiros não assinados (não negativos). Eu o adaptei, escrevi uma resposta que também funciona para números inteiros assinados.
Acumenus 11/01/19
1
Será que isso não ajudar com a obtenção b"3"de 3, como a questão pede. (Ele vai dar b"\x03".)
gsnedders
40

Você pode usar o pacote da estrutura :

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

O ">" é a ordem dos bytes (big endian) e o "I" é o caractere de formato . Portanto, você pode ser específico se desejar fazer outra coisa:

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

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

Isso funciona da mesma maneira no python 2 e no python 3 .

Nota: a operação inversa (bytes em int) pode ser feita com a descompactação .

Andy Hayden
fonte
2
@AndyHayden Para esclarecer, uma vez que uma estrutura tem um tamanho padrão, independentemente da entrada, I, H, e Btrabalho até 2**k - 1onde k é de 32, 16, e 8, respectivamente. Para insumos maiores, eles aumentam struct.error.
Acumenus
Presumivelmente down-votado como ele não responder à pergunta: o OP quer saber como gerar b'3\r\n', ou seja, um byte-string contendo o caractere ASCII "3" não o caráter ASCII "\ x03"
Dave Jones
1
@DaveJones O que faz você pensar que é isso que o OP quer? A resposta aceita retorna \x03e a solução, se você quiser, b'3'é trivial. A razão citada pela ABB é muito mais plausível ... ou pelo menos compreensível.
Andy Hayden
@DaveJones Além disso, a razão pela qual eu adicionei esta resposta foi porque o Google o leva aqui ao procurar fazer exatamente isso. Então é por isso que está aqui.
Andy Hayden
4
Isso não apenas funciona da mesma forma em 2 e 3, mas é mais rápido que os métodos bytes([x])e (x).to_bytes()no Python 3.5. Isso foi inesperado.
Mark Ransom
25

O Python 3.5+ apresenta% -interpolation ( printfformatação de estilo) para bytes :

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

Consulte PEP 0461 - Adicionando% de formatação a bytes e bytearray .

Nas versões anteriores, você poderia usar stre .encode('ascii')o resultado:

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

Nota: É diferente do que int.to_bytesproduz :

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

A documentação diz:

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

A sequência:

b'3\r\n'

É o caractere '3' (decimal 51) o caractere '\ r' (13) e '\ n' (10).

Portanto, a maneira como a trataria como tal, por exemplo:

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

Testado no IPython 1.1.0 e Python 3.2.3

Schcriher
fonte
1
Acabei fazendo bytes(str(n), 'ascii') + b'\r\n'ou str(n).encode('ascii') + b'\r\n'. Obrigado! :)
astrojuanlu
1
@ Juanlu001, também "{}\r\n".format(n).encode()eu não acho que haja qualquer dano feito usando a codificação UTF-8 padrão
John La Rooy
6

A ASCIIfication de 3 "\x33"não é "\x03"!

É para isso que o python faz, str(3)mas seria totalmente errado para bytes, pois eles devem ser considerados matrizes de dados binários e não devem ser abusados ​​como strings.

A maneira mais fácil de conseguir o que você quer é bytes((3,)), o que é melhor do que bytes([3])porque a inicialização de uma lista é muito mais cara, portanto, nunca use listas quando puder usar tuplas. Você pode converter números inteiros maiores usando int.to_bytes(3, "little").

A inicialização de bytes com um determinado comprimento faz sentido e é a mais útil, pois eles costumam ser usados ​​para criar algum tipo de buffer para o qual você precisa de alguma memória de determinado tamanho alocado. Costumo usar isso ao inicializar matrizes ou expandir algum arquivo escrevendo zeros nele.

Bachsau
fonte
1
Existem vários problemas com esta resposta: (a) A notação de escape de b'3'is b'\x33', not b'\x32'. (b) (3)não é uma tupla - você precisa adicionar uma vírgula. (c) O cenário de inicializar uma sequência com zeros não se aplica aos bytesobjetos, pois eles são imutáveis ​​(embora faça sentido para bytearrays).
Lenz
Obrigado por seu comentário. Corrigi esses dois erros óbvios. No caso de bytese bytearray, acho que é principalmente uma questão de consistência. Mas também é útil se você deseja inserir alguns zeros em um buffer ou arquivo; nesse caso, ele é usado apenas como fonte de dados.
Bachsau
5

int(incluindo Python2 long) pode ser convertido para a bytesseguinte função:

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

A conversão reversa pode ser feita por outra:

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 as funções funcionam no Python2 e Python3.

renskiy
fonte
'hex_value ='% x '% i' não funcionará no Python 3.4. Você recebe um TypeError, então você teria que usar hex ().
bjmc
@bjmc substituído por str.format. Isso deve funcionar no Python 2.6+.
precisa saber é o seguinte
Obrigado, @renskiy. Você pode querer usar 'hex_codec' em vez de 'hex' porque parece que o alias 'hex' não está disponível em todas as versões do Python 3, consulte stackoverflow.com/a/12917604/845210
bjmc
@bjmc fixed. Obrigado
renskiy
Isso falha em números inteiros negativos no python 3.6
Berserker
4

Eu estava curioso sobre o desempenho de vários métodos para um único int no intervalo [0, 255], então decidi fazer alguns testes de temporização.

Com base nos horários abaixo, e da tendência geral observei de tentar muitos valores e configurações diferentes, struct.packparece ser o mais rápido, seguido por int.to_bytes, bytese com str.encode(sem surpresa), sendo o mais lento. Note-se que os resultados mostram um pouco mais variação do que é representado, e int.to_bytese bytespor vezes ligado a velocidade escalão durante os testes, mas struct.packé claramente o mais rápido.

Resultados no CPython 3.7 no 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 teste (nomeado 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
fonte
1
@ABB Como mencionado na minha primeira frase, só estou medindo isso para um único int no intervalo [0, 255]. Presumo que por "indicador errado", você quer dizer que minhas medidas não foram gerais o suficiente para atender à maioria das situações? Ou minha metodologia de medição era ruim? Nesse último caso, eu estaria interessado em ouvir o que você tem a dizer, mas se o primeiro, nunca afirmei que minhas medidas fossem genéricas para todos os casos de uso. Para minha situação (talvez de nicho), estou lidando apenas com ints no intervalo [0, 255], e esse é o público que pretendi abordar com esta resposta. Minha resposta não estava clara? I pode editá-lo para maior clareza ...
Graham
1
E a técnica de indexar apenas uma codificação pré-computada para o intervalo? A pré-computação não estaria sujeita a tempo, apenas a indexação estaria.
Acumenus 11/01/19
@ABB Essa é uma boa ideia. Parece que será mais rápido do que qualquer outra coisa. Vou dar um tempo e adicioná-lo a esta resposta quando tiver algum tempo.
Graham
3
Se você realmente deseja cronometrar o número de bytes a partir de iterável, use-o em bytes((i,))vez de bytes([i])porque a lista é mais complexa, use mais memória e demore muito para inicializar. Neste caso, por nada.
Bachsau 28/03/19
4

Embora a resposta anterior do brunsgaard seja uma codificação eficiente, ela funciona apenas para números inteiros não assinados. Este baseia-se em trabalhar para números inteiros assinados e não assinados.

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 o codificador, (i + ((i * signed) < 0)).bit_length()é usado em vez de apenas i.bit_length()porque o último leva a uma codificação ineficiente de -128, -32768 etc.


Crédito: CervEd por corrigir uma pequena ineficiência.

Acumenus
fonte
int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)éFalse
CervEd 03/06/19
Você não está usando o comprimento 2, está calculando o tamanho do bit do número inteiro assinado, adicionando 7 e, em seguida, 1, se for um número inteiro assinado. Finalmente, você converte isso para o comprimento em bytes. Isso produz resultados inesperados para -128, -32768etc.
Cerved
É assim que você conserta(i+(signed*i<0)).bit_length()
CervEd
3

O comportamento vem do fato de que no Python anterior à versão 3 bytesera apenas um alias para str. No Python3.x bytesexiste uma versão imutável do bytearraytipo + completamente novo, não compatível com versões anteriores.

esquisito
fonte
3

Dos bytes docs :

Por conseguinte, os argumentos do construtor são interpretados como para bytearray ().

Em seguida, de bytearray docs :

O parâmetro de origem opcional pode ser usado para inicializar a matriz de algumas maneiras diferentes:

  • Se for um número inteiro, a matriz terá esse tamanho e será inicializada com bytes nulos.

Observe que isso difere do comportamento 2.x (onde x> = 6), onde bytesé simplesmente str:

>>> bytes is str
True

PEP 3112 :

O 2.6 str difere do tipo de bytes do 3.0 de várias maneiras; mais notavelmente, o construtor é completamente diferente.

alko
fonte
0

Algumas respostas não funcionam com números grandes.

Converta inteiro para a representação hexadecimal e, em seguida, converta-o em 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
fonte
1
"Todos os outros métodos não funcionam com grandes números". Isso não é verdade, int.to_bytesfunciona com qualquer número inteiro.
juanpa.arrivillaga 25/04
@ juanpa.arrivillaga sim, meu mal. Eu editei minha resposta.
Max Malysh
-1

Se a questão é como converter um número inteiro em si (não sua string equivalente) em bytes, acho que a resposta robusta é:

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

Mais informações sobre esses métodos aqui:

  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
fonte
1
Como isso difere da resposta de brunsgaard, publicada há 5 anos e atualmente a resposta mais votada?
Arthur Tacca