Conversion d'int en octets dans Python 3

178

J'essayais de construire cet objet bytes en Python 3:

b'3\r\n'

alors j'ai essayé l'évidence (pour moi), et j'ai trouvé un comportement bizarre:

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

Apparemment:

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

Je n'ai pas pu voir de pointeurs sur la raison pour laquelle la conversion d'octets fonctionne de cette façon en lisant la documentation. Cependant, j'ai trouvé des messages surprises dans ce problème Python concernant l'ajout formatd'octets (voir aussi le formatage Python 3 octets ):

http://bugs.python.org/issue3982

Cela interagit encore plus mal avec des bizarreries comme bytes (int) renvoyant des zéros maintenant

et:

Ce serait beaucoup plus pratique pour moi si bytes (int) renvoyait l'ASCIIfication de cet int; mais honnêtement, même une erreur serait meilleure que ce comportement. (Si je voulais ce comportement - que je n'ai jamais eu - je préférerais que ce soit une méthode de classe, invoquée comme "bytes.zeroes (n)".)

Quelqu'un peut-il m'expliquer d'où vient ce comportement?

astrojuanlu
la source
1
en rapport avec le titre:3 .to_bytes
jfs
2
Il n'est pas clair d'après votre question si vous voulez la valeur entière 3 ou la valeur du caractère ASCII représentant le numéro trois (valeur entière 51). Le premier est bytes ([3]) == b '\ x03'. Ce dernier est octets ([ord ('3')]) == b'3 '.
florisla

Réponses:

177

C'est ainsi qu'il a été conçu - et cela a du sens car généralement, vous appelez bytesun itérable au lieu d'un seul entier:

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

La documentation indique ceci , ainsi que la docstring pour bytes:

 >>> help(bytes)
 ...
 bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Tim Pietzcker
la source
25
Attention, ce qui précède ne fonctionne qu'avec python 3. En python 2 bytesest juste un alias pour str, ce qui signifie que bytes([3])vous donne '[3]'.
botchniaque
9
Dans Python 3, notez que cela bytes([n])ne fonctionne que pour int n de 0 à 255. Pour tout ce qu'il déclenche ValueError.
Acumenus
8
@ABB: Pas vraiment surprenant puisqu'un octet ne peut stocker que des valeurs comprises entre 0 et 255.
Tim Pietzcker
7
Il convient également de noter que bytes([3])c'est toujours différent de ce que l'OP voulait - à savoir la valeur d'octet utilisée pour coder le chiffre "3" en ASCII, c'est-à-dire. bytes([51]), ce qui n'est b'3'pas b'\x03'.
lenz
2
bytes(500)crée une chaîne d'octets w / len == 500. Elle ne crée pas une chaîne d'octets qui encode l'entier 500. Et je suis d'accord que bytes([500])cela ne peut pas fonctionner, c'est pourquoi c'est aussi la mauvaise réponse. La bonne réponse est probablement int.to_bytes()pour les versions> = 3.1.
weberc2
200

À partir de python 3.2, vous pouvez faire

>>> (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 conséquence x == int_from_bytes(int_to_bytes(x)),. Notez que cet encodage ne fonctionne que pour les entiers non signés (non négatifs).

Brunsgaard
la source
4
Bien que cette réponse soit bonne, elle ne fonctionne que pour les entiers non signés (non négatifs). Je l'ai adapté pour écrire une réponse qui fonctionne également pour les entiers signés.
Acumenus
1
Cela ne vous aide pas à obtenir b"3"de 3, comme la question demande. (Ça va donner b"\x03".)
gsnedders
41

Vous pouvez utiliser le pack de la structure :

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

Le ">" est l' ordre des octets (big-endian) et le "I" est le caractère de format . Vous pouvez donc être précis si vous souhaitez faire autre chose:

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

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

Cela fonctionne de la même manière sur python 2 et python 3 .

Remarque: l'opération inverse (octets en int) peut être effectuée avec unpack .

Andy Hayden
la source
2
@AndyHayden Pour clarifier, car une struct a une taille standard quelle que soit l'entrée, I, Het le Btravail jusqu'à ce que 2**k - 1k est de 32, 16 et 8 respectivement. Pour des intrants plus importants, ils augmentent struct.error.
Acumenus
Vraisemblablement voté à la baisse car il ne répond pas à la question: l'OP veut savoir comment générer b'3\r\n', c'est-à-dire une chaîne d'octets contenant le caractère ASCII "3" et non le caractère ASCII "\ x03"
Dave Jones
1
@DaveJones Qu'est-ce qui vous fait penser que c'est ce que veut l'OP? La réponse acceptée revient \x03, et la solution si vous voulez juste b'3'est triviale. La raison invoquée par ABB est beaucoup plus plausible ... ou du moins compréhensible.
Andy Hayden
@DaveJones Aussi, la raison pour laquelle j'ai ajouté cette réponse était que Google vous emmène ici lorsque vous cherchez à faire précisément cela. C'est pourquoi c'est ici.
Andy Hayden
5
Non seulement cela fonctionne de la même manière en 2 et 3, mais c'est plus rapide que les méthodes bytes([x])et (x).to_bytes()de Python 3.5. C'était inattendu.
Mark Ransom
25

Python 3.5+ introduit% -interpolation ( printfformatage -style) pour les octets :

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

Voir PEP 0461 - Ajout de% de mise en forme aux octets et aux bytearray .

Sur les versions antérieures, vous pouvez utiliser stret .encode('ascii')le résultat:

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

Remarque: il est différent de ce qui int.to_bytesproduit :

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

La documentation dit:

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

La séquence:

b'3\r\n'

C'est le caractère '3' (décimal 51) le caractère '\ r' (13) et '\ n' (10).

Par conséquent, la manière de le traiter comme tel, par exemple:

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

Testé sur IPython 1.1.0 et Python 3.2.3

Schcriher
la source
1
J'ai fini par faire bytes(str(n), 'ascii') + b'\r\n'ou str(n).encode('ascii') + b'\r\n'. Merci! :)
astrojuanlu
1
@ Juanlu001, "{}\r\n".format(n).encode()je ne pense pas non plus qu'il y ait de mal à utiliser le codage utf8 par défaut
John La Rooy
6

L'ASCIIfication de 3 "\x33"ne l' est pas "\x03"!

C'est ce que fait python, str(3)mais ce serait totalement faux pour les octets, car ils devraient être considérés comme des tableaux de données binaires et ne pas être abusés comme des chaînes.

Le moyen le plus simple d'obtenir ce que vous voulez est bytes((3,)), ce qui est mieux que bytes([3])parce que l' initialisation d'une liste est beaucoup plus coûteuse, donc n'utilisez jamais de listes lorsque vous pouvez utiliser des tuples. Vous pouvez convertir des entiers plus grands en utilisant int.to_bytes(3, "little").

L'initialisation d'octets avec une longueur donnée a du sens et est la plus utile, car ils sont souvent utilisés pour créer un type de tampon pour lequel vous avez besoin d'une mémoire d'une taille donnée allouée. J'utilise souvent ceci lors de l'initialisation des tableaux ou de l'extension d'un fichier en y écrivant des zéros.

Bachsau
la source
1
Il y a plusieurs problèmes avec cette réponse: (a) La notation d'échappement de b'3'is b'\x33', not b'\x32'. (b) (3)n'est pas un tuple - vous devez ajouter une virgule. (c) Le scénario d'initialisation d'une séquence avec des zéros ne s'applique pas aux bytesobjets, car ils sont immuables (cela a du sens pour bytearrays, cependant).
lenz
Merci pour votre commentaire. J'ai corrigé ces deux erreurs évidentes. Dans le cas de byteset bytearray, je pense que c'est surtout une question de cohérence. Mais c'est également utile si vous souhaitez insérer des zéros dans un tampon ou un fichier, auquel cas il n'est utilisé que comme source de données.
Bachsau
5

int(y compris Python2 long) peuvent être convertis en bytesutilisant la fonction suivante:

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 conversion inverse peut être effectuée par un autre:

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)

Les deux fonctions fonctionnent à la fois sur Python2 et Python3.

renskiy
la source
'hex_value ='% x '% i' ne fonctionnera pas sous Python 3.4. Vous obtenez une TypeError, vous devrez donc utiliser hex () à la place.
bjmc
@bjmc remplacé par str.format. Cela devrait fonctionner sur Python 2.6+.
renskiy
Merci, @renskiy. Vous voudrez peut-être utiliser 'hex_codec' au lieu de 'hex' car il semble que l'alias 'hex' ne soit pas disponible sur toutes les versions de Python 3 voir stackoverflow.com/a/12917604/845210
bjmc
@bjmc corrigé. Merci
renskiy
Cela échoue sur les entiers négatifs sur python 3.6
Berserker
4

J'étais curieux de connaître les performances de diverses méthodes pour un seul int de la gamme [0, 255], alors j'ai décidé de faire des tests de timing.

Sur la base des horaires ci - dessous, et de la tendance générale , je remarquai d'essayer de nombreuses valeurs et configurations, struct.packsemble être le plus rapide, suivi int.to_bytes, byteset avec str.encode(sans surprise) étant le plus lent. Notez que les résultats montrent un peu plus de variation que ce qui est représenté, int.to_byteset bytesparfois changé de classement de vitesse pendant les tests, maisstruct.pack c'est clairement le plus rapide.

Résultats dans CPython 3.7 sous 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

Module de test (nommé 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
la source
1
@ABB Comme mentionné dans ma première phrase, je ne mesure cela que pour un seul int dans la plage [0, 255]. Je suppose que par "mauvais indicateur" vous voulez dire que mes mesures n'étaient pas assez générales pour s'adapter à la plupart des situations? Ou ma méthodologie de mesure était-elle médiocre? Si le dernier, je serais intéressé d'entendre ce que vous avez à dire, mais si le premier, je n'ai jamais prétendu que mes mesures étaient génériques pour tous les cas d'utilisation. Pour ma situation (peut-être de niche), je ne traite que des ints de la gamme [0, 255], et c'est à ce public que je comptais m'adresser avec cette réponse. Ma réponse n'était-elle pas claire? Je peux le modifier pour plus de clarté ...
Graham
1
Qu'en est-il de la technique qui consiste simplement à indexer un codage précalculé pour la plage? Le précalcul ne serait pas soumis au timing, seule l'indexation le serait.
Acumenus
@ABB C'est une bonne idée. Cela semble être plus rapide qu'autre chose. Je vais chronométrer et l'ajouter à cette réponse quand j'aurai le temps.
Graham
3
Si vous voulez vraiment chronométrer la chose octets à partir de l'itération, vous devriez utiliser à la bytes((i,))place de bytes([i])parce que les listes sont plus complexes, utilisent plus de mémoire et prennent du temps à s'initialiser. Dans ce cas, pour rien.
Bachsau
4

Bien que la réponse précédente de brunsgaard soit un encodage efficace, elle ne fonctionne que pour les entiers non signés. Celui-ci s'appuie sur lui pour fonctionner à la fois pour les entiers signés et non signés.

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)

Pour le codeur, (i + ((i * signed) < 0)).bit_length()est utilisé au lieu de simplement i.bit_length()parce que ce dernier conduit à un codage inefficace de -128, -32768, etc.


Crédit: CervEd pour avoir corrigé une inefficacité mineure.

Acumenus
la source
int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)isFalse
CervEd
Vous n'utilisez pas la longueur 2, vous calculez la longueur en bits de l'entier signé, en ajoutant 7, puis 1, s'il s'agit d'un entier signé. Enfin, vous convertissez cela en longueur en octets. Cela donne des résultats inattendus pour -128, -32768etc.
CervEd
Continuons cette discussion en chat .
CervEd
C'est ainsi que vous le (i+(signed*i<0)).bit_length()
résolvez
3

Le comportement vient du fait que dans Python avant la version 3 bytesétait juste un alias pour str. Dans Python3.x bytesest une version immuable de bytearray- type complètement nouveau, pas rétrocompatible.

bizarre
la source
3

À partir de la documentation d'octets :

En conséquence, les arguments du constructeur sont interprétés comme pour bytearray ().

Ensuite, à partir de la documentation bytearray :

Le paramètre source facultatif peut être utilisé pour initialiser le tableau de différentes manières:

  • S'il s'agit d'un entier, le tableau aura cette taille et sera initialisé avec des octets nuls.

Notez que cela diffère du comportement 2.x (où x> = 6), où bytesest simplement str:

>>> bytes is str
True

PEP 3112 :

La chaîne 2.6 diffère du type d'octets de 3.0 de plusieurs manières; plus particulièrement, le constructeur est complètement différent.

alko
la source
0

Certaines réponses ne fonctionnent pas avec de grands nombres.

Convertissez un entier en représentation hexadécimale, puis convertissez-le en octets:

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

Résultat:

>>> 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
la source
1
"Toutes les autres méthodes ne fonctionnent pas avec de grands nombres." Ce n'est pas vrai, int.to_bytesfonctionne avec n'importe quel entier.
juanpa.arrivillaga
@ juanpa.arrivillaga oui, mon mal. J'ai modifié ma réponse.
Max Malysh
-1

Si la question est de savoir comment convertir un entier lui-même (et non son équivalent chaîne) en octets, je pense que la réponse robuste est:

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

Plus d'informations sur ces méthodes ici:

  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
la source
1
En quoi est-ce différent de la réponse de brunsgaard, publiée il y a 5 ans et actuellement la réponse la plus votée?
Arthur Tacca