Int naar bytes converteren in Python 3

Ik probeerde dit bytes-object te bouwen in Python 3:

b'3\r\n'

dus ik probeerde het voor de hand liggende (voor mij), en vond vreemd gedrag:

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

Blijkbaar:

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

Ik heb geen aanwijzingen kunnen vinden waarom de bytes-conversie op deze manier werkt bij het lezen van de documentatie. Ik vond echter enkele verrassende berichten in dit Python-nummer over het toevoegen van formataan bytes (zie ook Python 3 bytes opmaak):

http://bugs.python.org/issue3982

Dit werkt nog slechter samen met eigenaardigheden zoals bytes(int) die nu nullen retourneren

en:

Het zou voor mij veel handiger zijn als bytes(int) de ASCIIficatie van die int terugstuurde; maar eerlijk gezegd zou zelfs een fout beter zijn dan dit gedrag. (Als ik dit gedrag zou willen – wat ik nooit heb – zou ik liever een klassenmethode hebben, aangeroepen als “bytes.zeroes(n)”.)

Kan iemand me uitleggen waar dit gedrag vandaan komt?


Antwoord 1, autoriteit 100%

Dat is de manier waarop het is ontworpen – en het is logisch omdat je normaal gesproken byteszou aanroepen op een iterabel in plaats van op een enkel geheel getal:

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

De docs geven dit aan, evenals de docstring voor bytes:

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

Antwoord 2, autoriteit 95%

Vanaf python 3.2 kun je doen

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

Dienovereenkomstig x == int_from_bytes(int_to_bytes(x)).
Merk op dat de bovenstaande codering alleen werkt voor niet-ondertekende (niet-negatieve) gehele getallen.

Voor getekende gehele getallen is de bitlengte wat lastiger om te berekenen:

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)

Antwoord 3, autoriteit 20%

U kunt het struct-pakketgebruiken:

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

De “>” is de byte-order (big-endian)en de “I” is het opmaakteken. U kunt dus specifiek zijn als u iets anders wilt doen:

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

Dit werkt hetzelfde op zowel python 2 als python 3.

Opmerking: de inverse bewerking (bytes naar int) kan worden gedaan met uitpakken.


Antwoord 4, autoriteit 12%

Python 3.5+ introduceert %-interpolatie (printf-stijl opmaak) voor bytes:

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

Zie PEP 0461 — % opmaak toevoegen aan bytes en bytearray.

In eerdere versies kon je stren .encode('ascii')gebruiken, het resultaat:

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

Opmerking: het is anders dan wat int.to_bytesproduceert:

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

Antwoord 5, Autoriteit 5%

De documentatie zegt:

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

De volgorde:

b'3\r\n'

Het is het teken ‘3’ (decimaal 51) het teken ‘\ r’ (13) en ‘\ n’ (10).

Daarom zou de weg het als zodanig behandelen, bijvoorbeeld:

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

Getest op IPYTHON 1.1.0 & amp; Python 3.2.3


Antwoord 6, Autoriteit 3%

De asciificatie van 3 is "\x33"niet "\x03"!

Dat is wat Python doet voor str(3), maar het zou totaal verkeerd zijn voor bytes, omdat ze als arrays van binaire gegevens moeten worden beschouwd en niet als snaren kunnen worden misbruikt.

De eenvoudigste manier om te bereiken wat u wilt is bytes((3,)), wat beter is dan bytes([3])omdat het initialiseren van een lijst is Veel duurder, dus gebruik nooit lijsten wanneer u tuples kunt gebruiken. U kunt grotere gehele getallen converteren met behulp van int.to_bytes(3, "little").

Initialiseren van bytes met een bepaalde lengte is logisch en is het nuttigst, omdat ze vaak worden gebruikt om een ​​soort buffer te creëren waarvoor u een geheugen nodig hebt van toegewezen grootte. Ik gebruik dit vaak bij het initialiseren van de arrays of het uitbreiden van een bestand door erover te schrijven.


Antwoord 7, Autoriteit 2%

int(inclusief Python2’s long) kan worden geconverteerd naar bytesmet behulp van de volgende functie:

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

De omgekeerde conversie kan worden gedaan door een andere:

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)

Beide functies werken op zowel Python2 als Python3.


Antwoord 8, autoriteit 2%

Ik was benieuwd naar de prestaties van verschillende methoden voor een enkele int in het bereik [0, 255], dus besloot ik een aantal timingtests te doen.

Op basis van de onderstaande timing en de algemene trend die ik heb waargenomen bij het uitproberen van veel verschillende waarden en configuraties, lijkt struct.packde snelste te zijn, gevolgd door int.to_bytes, bytes, en met str.encode(niet verrassend) als de langzaamste. Merk op dat de resultaten wat meer variatie laten zien dan wordt weergegeven, en int.to_bytesen byteswisselden soms van snelheidsrangorde tijdens het testen, maar struct.packis duidelijk de snelste.

Resultaten in CPython 3.7 op 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

Testmodule (genaamd 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)'"
        ))

Antwoord 9, autoriteit 2%

Hoewel het eerdere antwoord van brunsgaardeen efficiënte codering is, werkt het alleen voor niet-ondertekende gehele getallen. Deze bouwt erop voort om te werken voor zowel ondertekende als niet-ondertekende gehele getallen.

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)

Voor de encoder wordt (i + ((i * signed) < 0)).bit_length()gebruikt in plaats van alleen i.bit_length()omdat dit laatste leidt tot een inefficiënte codering van -128, -32768, enz.


Credit: CervEd voor het oplossen van een kleine inefficiëntie.


Antwoord 10

Het gedrag komt voort uit het feit dat in Python vóór versie 3 bytesslechts een alias was voor str. In Python3.x is byteseen onveranderlijke versie van bytearray– volledig nieuw type, niet achterwaarts compatibel.


Antwoord 11

Van bytes docs:

Dienovereenkomstig worden constructorargumenten geïnterpreteerd als voor bytearray().

Dan, uit bytearray-documenten:

De optionele bronparameter kan worden gebruikt om de array op een paar verschillende manieren te initialiseren:

  • Als het een geheel getal is, heeft de array die grootte en wordt deze geïnitialiseerd met null-bytes.

Let op, dat verschilt van 2.x (waar x >= 6) gedrag, waar bytesgewoon stris:

>>> bytes is str
True

PEP 3112:

De 2.6 str verschilt op verschillende manieren van het bytes-type van 3.0; met name de constructor is compleet anders.


Antwoord 12

Sommige antwoorden werken niet met grote getallen.

Converteer geheel getal naar de hexadecimale weergave en converteer het vervolgens naar bytes:

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

Resultaat:

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

Antwoord 13

Omdat u met binaire weergave wilt omgaan, kunt u het beste ctypesgebruiken.

import ctypes
x = ctypes.c_int(1234)
bytes(x)

U moet de specifieke integer-weergave gebruiken (ondertekend / niet-ondertekend en het aantal bits: c_uint8, c_int8, c_unit16, … ).


Antwoord 14

Ik denk dat je de int naar str eerst kunt converteren voordat je naar Byte converteert.
Dat zou het gewenste formaat moeten produceren.

bytes(str(your_number),'UTF-8') + b'\r\n'

Het werkt voor mij in PY3.8.


Antwoord 15

Als de vraag een geheel getal zelf kan converteren (niet zijn string-equivalent) in bytes, denk ik dat het robuuste antwoord is:

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

Meer informatie over deze methoden hier:

  1. https://docs.python.org/3.8/Library /sttypes.html#int.to_bytes
  2. https://docs.python.org/3.8/Library /sttypes.html#int.from_bytes

Other episodes