Konwersja int na bajty w Pythonie 3

177

Próbowałem zbudować ten obiekt bajtów w Pythonie 3:

b'3\r\n'

więc wypróbowałem oczywiste (dla mnie) i znalazłem dziwne zachowanie:

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

Widocznie:

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

Nie udało mi się znaleźć żadnych wskazówek, dlaczego konwersja bajtów działa w ten sposób, czytając dokumentację. Jednak w tym numerze Pythona znalazłem niespodziewane komunikaty dotyczące dodawania formatdo bajtów (zobacz także formatowanie 3 bajtów w Pythonie ):

http://bugs.python.org/issue3982

To współdziała teraz jeszcze gorzej z dziwactwami, takimi jak bajty (int) zwracające zera

i:

Byłoby dla mnie o wiele wygodniejsze, gdyby bytes (int) zwróciło ASCIIfication tego int; ale szczerze, nawet błąd byłby lepszy niż to zachowanie. (Gdybym chciał tego zachowania - którego nigdy nie miałem - wolałbym, żeby to była metoda klasowa, wywoływana jak „bytes.zeroes (n)”.)

Czy ktoś może mi wyjaśnić, skąd bierze się to zachowanie?

astrojuanlu
źródło
1
związane z tytułem:3 .to_bytes
jfs
2
Z twojego pytania nie wynika jasno, czy chcesz uzyskać wartość całkowitą 3, czy wartość znaku ASCII reprezentującego liczbę trzy (wartość całkowita 51). Pierwsza to bajty ([3]) == b '\ x03'. Ta ostatnia to bajty ([ord ('3')]) == b'3 '.
florisla

Odpowiedzi:

177

W ten sposób został zaprojektowany - i ma to sens, ponieważ zwykle wywołujesz bytesiterowalną zamiast pojedynczej liczby całkowitej:

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

Dokumentacja to stwierdza , a także dokumentacja bytes:

 >>> help(bytes)
 ...
 bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Tim Pietzcker
źródło
25
Uważaj, powyższe działa tylko z pythonem 3. W pythonie 2 bytesto tylko alias dla str, co oznacza, bytes([3])że daje '[3]'.
botchniaque
8
W Pythonie 3 zauważ, że bytes([n])działa tylko dla int n od 0 do 255. W przypadku czegokolwiek innego podnosi ValueError.
Acumenus
8
@ABB: Nic dziwnego, ponieważ bajt może przechowywać tylko wartości od 0 do 255.
Tim Pietzcker
7
Należy również zauważyć, że bytes([3])nadal różni się od tego, czego chciał OP - a mianowicie wartości bajtu używanej do zakodowania cyfry „3” w ASCII, tj. bytes([51])czyli b'3'nie b'\x03'.
lenz
2
bytes(500)tworzy bajtest z / len == 500. Nie tworzy bajtestu, który koduje liczbę całkowitą 500. I zgadzam się, że bytes([500])to nie może działać, dlatego też jest to zła odpowiedź. Prawdopodobnie prawidłowa odpowiedź to int.to_bytes()wersja> = 3.1.
weberc2
198

Od Pythona 3.2 możesz to zrobić

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

W związku z tym x == int_from_bytes(int_to_bytes(x)). Zauważ, że to kodowanie działa tylko dla liczb całkowitych bez znaku (nieujemnych).

brunsgaard
źródło
4
Chociaż ta odpowiedź jest dobra, działa tylko dla liczb całkowitych bez znaku (nieujemnych). Dostosowałem to, napisz odpowiedź, która działa również dla liczb całkowitych ze znakiem.
Acumenus,
1
To nie pomaga w uzyskaniu b"3"od 3, jak zagadnienie pyta. (To da b"\x03".)
gsnedders
40

Możesz użyć paczki struktury :

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

„>” To kolejność bajtów (big-endian), a „I” to znak formatu . Możesz więc być konkretny, jeśli chcesz zrobić coś innego:

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

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

Działa to tak samo na Pythonie 2 i Pythonie 3 .

Uwaga: operację odwrotną (bajty do int) można wykonać przy rozpakowywaniu .

Andy Hayden
źródło
2
@AndyHayden celu wyjaśnienia, gdyż struktura ma standardową wielkość, niezależnie od wejścia, I, H, i Bpraca aż 2**k - 1gdzie k jest 32, 16 i 8 odpowiednio. W przypadku większych nakładów podnoszą struct.error.
Acumenus
Przypuszczalnie dół głosowało ponieważ nie odpowiedzieć na pytanie: PO chce wiedzieć jak generować b'3\r\n', czyli bajt łańcuch zawierający znak ASCII „3” a nie znaków ASCII „\ X03”
Dave Jones
1
@DaveJones Co sprawia, że ​​myślisz, że tego chce OP? W zaakceptowanych odpowiedź powraca \x03, a rozwiązanie, jeśli chcesz po prostu b'3'jest trywialne. Powód przytoczony przez ABB jest znacznie bardziej prawdopodobny ... lub przynajmniej zrozumiały.
Andy Hayden,
@DaveJones Powodem, dla którego dodałem tę odpowiedź, było to, że Google zabiera Cię tutaj podczas wyszukiwania, aby zrobić dokładnie to. Dlatego właśnie tu jest.
Andy Hayden,
4
Nie tylko działa to tak samo w 2 i 3, ale jest szybsze niż metody bytes([x])i (x).to_bytes()w Pythonie 3.5. To było niespodziewane.
Mark Ransom
25

Python 3.5+ wprowadza interpolację% ( printfformatowanie stylu) dla bajtów :

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

Zobacz PEP 0461 - Dodawanie formatowania% do bajtów i bajtów .

We wcześniejszych wersjach możesz użyć stri .encode('ascii')wynik:

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

Uwaga: różni się od tego, co int.to_bytesprodukuje :

>>> n = 3
>>> n.to_bytes((n.bit_length() + 7) // 8, 'big') or b'\0'
b'\x03'
>>> b'3' == b'\x33' != '\x03'
True
jfs
źródło
11

Dokumentacja mówi:

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

Sekwencja:

b'3\r\n'

Jest to znak „3” (dziesiętnie 51), znak „\ r” (13) i „\ n” (10).

Dlatego sposób potraktowałby to jako takie, na przykład:

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

Przetestowano na IPythonie 1.1.0 i Pythonie 3.2.3

Schcriher
źródło
1
Skończyło się na zrobieniu bytes(str(n), 'ascii') + b'\r\n'lub str(n).encode('ascii') + b'\r\n'. Dzięki! :)
astrojuanlu
1
@ Juanlu001, również "{}\r\n".format(n).encode()nie sądzę, aby było coś złego przy użyciu domyślnego kodowania utf8
John La Rooy,
6

ASCIIfication of 3 "\x33"nie jest"\x03" !

Do tego służy Python str(3) ale byłoby to całkowicie błędne w przypadku bajtów, ponieważ powinny być traktowane jako tablice danych binarnych i nie powinny być nadużywane jako łańcuchy.

Najłatwiejszym sposobem osiągnięcia tego, co chcesz, jest to bytes((3,)), co jest lepsze niż bytes([3])inicjowanie listy jest znacznie droższe, więc nigdy nie używaj list, gdy możesz używać krotek. Możesz konwertować większe liczby całkowite za pomocąint.to_bytes(3, "little") .

Inicjalizacja bajtów o określonej długości ma sens i jest najbardziej przydatna, ponieważ często są używane do tworzenia pewnego typu bufora, dla którego potrzebujesz przydzielonej pamięci o podanym rozmiarze. Często używam tego podczas inicjowania tablic lub rozszerzania jakiegoś pliku przez zapisanie w nim zer.

Bachsau
źródło
1
Z tą odpowiedzią b'3'wiąże się kilka problemów: (a) Zapis ucieczki jest b'\x33', nie b'\x32'. (b) (3)nie jest krotką - musisz dodać przecinek. (c) Scenariusz inicjalizacji ciągu zerami nie dotyczy bytesobiektów, ponieważ są one niezmienne (ma to jednak sens dla bytearrays).
lenz
Dzięki za komentarz. Poprawiłem te dwa oczywiste błędy. W przypadku bytesi bytearraymyślę, że jest to głównie kwestia spójności. Ale jest to również przydatne, jeśli chcesz wstawić kilka zer do bufora lub pliku, w którym to przypadku jest on używany tylko jako źródło danych.
Bachsau
5

int(w tym Python2 long) można przekonwertować na bytesnastępującą funkcję:

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

Odwrotną konwersję można wykonać inną:

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)

Obie funkcje działają zarówno w Python2, jak i Python3.

renskij
źródło
'hex_value ='% x '% i' nie będzie działać pod Pythonem 3.4. Otrzymujesz TypeError, więc zamiast tego musisz użyć hex ().
bjmc
@bjmc zastąpione przez str.format. To powinno działać na Pythonie 2.6+.
renskiy
Dzięki, @renskiy. Możesz użyć „hex_codec” zamiast „hex”, ponieważ wygląda na to, że alias „hex” nie jest dostępny we wszystkich wydaniach Pythona 3, patrz stackoverflow.com/a/12917604/845210
bjmc
Naprawiono @bjmc. Dzięki
renskiy
To kończy się niepowodzeniem na ujemnych liczbach całkowitych w Pythonie 3.6
Berserker
4

Byłem ciekawy wydajności różnych metod dla pojedynczego int z zakresu [0, 255], więc postanowiłem zrobić kilka testów czasowych.

Na podstawie poniższych taktowania oraz od ogólnego trendu obserwowanego od I próbuje różne wartości i konfiguracje, struct.packwydaje się być najszybszy, a następnie int.to_bytes, bytesiz str.encode(nic dziwnego) jest najwolniejsze. Zwróć uwagę, że wyniki pokazują nieco więcej odchyleń niż przedstawiono, int.to_bytesi bytesczasami zmieniają ranking prędkości podczas testowania, alestruct.pack jest wyraźnie najszybszy.

Wyniki w CPython 3.7 w systemie 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

Moduł testowy (nazwany 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
źródło
1
@ABB Jak wspomniałem w pierwszym zdaniu, mierzę to tylko dla pojedynczego int w zakresie [0, 255]. Zakładam, że przez „zły wskaźnik” masz na myśli, że moje pomiary nie były wystarczająco ogólne, aby pasowały do ​​większości sytuacji? A może moja metodologia pomiaru była słaba? Jeśli to drugie, chciałbym usłyszeć, co masz do powiedzenia, ale jeśli to pierwsze, nigdy nie twierdziłem, że moje pomiary są ogólne dla wszystkich przypadków użycia. W mojej (być może niszowej) sytuacji mam do czynienia tylko z intami z zakresu [0, 255], a to jest publiczność, do której zamierzałem się zwrócić za pomocą tej odpowiedzi. Czy moja odpowiedź była niejasna? Mogę to zmienić dla jasności ...
Graham,
1
A co z techniką po prostu indeksowania wstępnie obliczonego kodowania dla zakresu? Obliczanie wstępne nie podlegałoby synchronizacji, tylko indeksowanie.
Acumenus
@ABB To dobry pomysł. Wygląda na to, że będzie szybszy niż cokolwiek innego. Zrobię trochę czasu i dodam to do tej odpowiedzi, kiedy będę miał trochę czasu.
Graham
3
Jeśli naprawdę chcesz mieć czas na iterowalną wartość bajtów, powinieneś użyć bytes((i,))zamiast tego, bytes([i])ponieważ lista jest bardziej złożona, zużywa więcej pamięci i zajmuje dużo czasu na inicjalizację. W tym przypadku na nic.
Bachsau
4

Chociaż wcześniejsza odpowiedź udzielona przez brunsgaarda jest wydajnym kodowaniem, działa tylko w przypadku liczb całkowitych bez znaku. Ten opiera się na nim, aby działał zarówno dla liczb całkowitych ze znakiem, jak i bez znaku.

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)

W przypadku kodera (i + ((i * signed) < 0)).bit_length()jest używany zamiast tylko i.bit_length()dlatego, że ten ostatni prowadzi do nieefektywnego kodowania -128, -32768 itp.


Kredyt: CervEd za naprawienie drobnej nieefektywności.

Acumenus
źródło
int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)jestFalse
CervEd
Nie używasz długości 2, obliczasz długość bitową liczby całkowitej ze znakiem, dodając 7, a następnie 1, jeśli jest to liczba całkowita ze znakiem. Na koniec konwertujesz to na długość w bajtach. Daje to nieoczekiwane rezultaty w -128, -32768itd.
Cerved
Oto jak to naprawić(i+(signed*i<0)).bit_length()
CervEd
3

Zachowanie wynika z faktu, że w Pythonie przed wersją 3 bytesbył tylko alias dla str. W Python3.x bytesjest niezmienną wersją bytearray- zupełnie nowego typu, niekompatybilnego wstecz.

kapryśny
źródło
3

Z dokumentów bajtów :

W związku z tym argumenty konstruktora są interpretowane jak dla bytearray ().

Następnie z dokumentów bytearray :

Opcjonalnego parametru source można użyć do zainicjowania tablicy na kilka różnych sposobów:

  • Jeśli jest to liczba całkowita, tablica będzie miała ten rozmiar i zostanie zainicjowana bajtami o wartości null.

Zauważ, że różni się to od zachowania 2.x (gdzie x> = 6), gdzie bytesjest po prostu str:

>>> bytes is str
True

PEP 3112 :

Typ 2.6 różni się od typu bajtów 3.0 na różne sposoby; przede wszystkim konstruktor jest zupełnie inny.

alko
źródło
0

Niektóre odpowiedzi nie działają z dużymi liczbami.

Zamień liczbę całkowitą na reprezentację szesnastkową, a następnie zamień ją na bajty:

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

Wynik:

>>> 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
źródło
1
„Wszystkie inne metody nie działają z dużymi liczbami”. To nieprawda, int.to_bytesdziała z dowolną liczbą całkowitą.
juanpa.arrivillaga
@ juanpa.arrivillaga tak, moja wina. Zmieniłem odpowiedź.
Max Malysh
-1

Jeśli pytanie brzmi, jak przekonwertować samą liczbę całkowitą (a nie jej odpowiednik w postaci ciągu) na bajty, myślę, że solidna odpowiedź brzmi:

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

Więcej informacji o tych metodach tutaj:

  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.
źródło
1
Czym różni się to od odpowiedzi brunsgaard, opublikowanej 5 lat temu i obecnie największej głosowanej odpowiedzi?
Arthur Tacca