web-dev-qa-db-fra.com

Convertir int en octets dans Python 3

J'essayais de construire cet objet octets dans Python 3:

b'3\r\n'

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

>>> 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'arrive pas à comprendre pourquoi la conversion d'octets fonctionne de cette manière en lisant la documentation. Cependant, dans ce numéro de Python, j'ai trouvé des messages surprises concernant l'ajout de format aux octets (voir aussi Python 3 octets ):

http://bugs.python.org/issue3982

Cela interagit encore plus mal avec des bizarreries telles que les octets (int) qui renvoient des zéros maintenant

et:

Ce serait beaucoup plus pratique pour moi si bytes (int) renvoyait la spécification ASCII de cet int; mais honnêtement, même une erreur serait préférable à 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, appelée comme "bytes.zeroes (n)".)

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

104
astrojuanlu

C’est la façon dont il a été conçu - et cela a du sens car habituellement, on appellerait bytes sur un itératif au lieu d’un entier:

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

Les docs indiquent ceci , ainsi que la docstring pour bytes:

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

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):
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')

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

En conséquence, x == int_from_bytes(int_to_bytes(x)).

122
brunsgaard

Vous pouvez utiliser le pack de struct :

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

Le ">" est le ordre des octets (big-endian) } et le "I" est le caractère de formatage . Vous pouvez donc être spécifique si vous voulez 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 vers int) peut être effectuée avec décompresser .

32
Andy Hayden

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

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

Voir PEP 0461 - Ajout du formatage en octets et bytearray .

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

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

Note: Cela diffère de ce que int.to_bytes produit :

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

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 façon 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

10
Schcriher

La ASCIIfication de 3 est "\x33" pas "\x03"!

C'est ce que python fait pour 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 utilisés comme des chaînes.

Le moyen le plus simple d’atteindre ce que vous voulez est bytes((3,)), qui est meilleur que bytes([3]) car l’initialisation d’une liste coûte beaucoup plus cher; évitez donc d’utiliser des listes lorsque vous pouvez utiliser des tuples. Vous pouvez convertir des entiers plus grands en utilisant int.to_bytes(3, "little").

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

5
Bachsau

int (y compris long de Python2) peut être converti en bytes à l'aide de 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.

5
renskiy

De octets docs :

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

Puis, à partir de bytearray docs :

Le paramètre source optionnel 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 ce comportement diffère du comportement 2.x (où x> = 6), où bytes est simplement str:

>>> bytes is str
True

PEP 3112 :

La str de 2.6 diffère du type d’octets de 3.0 de différentes manières; en particulier, le constructeur est complètement différent. 

3
alko

Le comportement provient du fait que, dans Python, avant la version 3, bytes était simplement un alias pour str. Dans Python3.x, bytes est une version immuable de bytearray - un type complètement nouveau, non compatible avec les versions antérieures.

3
freakish

J'étais curieux des performances de différentes méthodes pour un seul int de la gamme [0, 255], alors j'ai décidé de faire des tests de chronométrage.

D'après les timings ci-dessous et d'après la tendance générale que j'ai observée en essayant différentes valeurs et configurations, struct.pack semble être le plus rapide, suivi de int.to_bytes, bytes et de str.encode (sans surprise) étant le plus lent. Notez que les résultats montrent plus de variation que ce qui est représenté, et int.to_bytes et bytes changent parfois de classement de vitesse pendant les tests, mais struct.pack 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)'"
        ))
2
Graham

Bien que la précédente réponse de brunsgaard soit un encodage efficace, elle ne fonctionne que pour les entiers non signés. Celui-ci s'appuie dessus 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.bit_length() + 7 + int(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)
0
A-B-B