Mengubah int menjadi byte dalam Python 3

177

Saya mencoba untuk membangun objek byte ini di Python 3:

b'3\r\n'

jadi saya mencoba yang jelas (untuk saya), dan menemukan perilaku aneh:

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

Tampaknya:

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

Saya tidak dapat melihat petunjuk tentang mengapa konversi byte bekerja dengan cara ini membaca dokumentasi. Namun, saya menemukan beberapa pesan kejutan dalam masalah Python ini tentang menambahkan formatke byte (lihat juga pemformatan Python 3 byte ):

http://bugs.python.org/issue3982

Ini berinteraksi lebih buruk lagi dengan keanehan seperti byte (int) mengembalikan nol sekarang

dan:

Akan jauh lebih nyaman bagi saya jika byte (int) mengembalikan ASCIIfication dari int itu; tetapi jujur, bahkan kesalahan akan lebih baik daripada perilaku ini. (Jika saya menginginkan perilaku ini - yang tidak pernah saya miliki - saya lebih suka itu menjadi metode kelas, dipanggil seperti "bytes.zeroes (n)".)

Dapatkah seseorang menjelaskan kepada saya dari mana perilaku ini berasal?

astrojuanlu
sumber
1
terkait dengan judul:3 .to_bytes
jfs
2
Tidak jelas dari pertanyaan Anda jika Anda menginginkan nilai integer 3, atau nilai karakter ASCII mewakili angka tiga (nilai integer 51). Yang pertama adalah byte ([3]) == b '\ x03'. Yang terakhir adalah byte ([ord ('3')]) == b'3 '.
florisla

Jawaban:

177

Begitulah cara itu dirancang - dan itu masuk akal karena biasanya, Anda akan memanggil bytesiterable daripada integer tunggal:

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

The docs menyatakan ini , serta docstring untuk bytes:

 >>> help(bytes)
 ...
 bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Tim Pietzcker
sumber
25
Berhati-hatilah karena cara di atas hanya berfungsi dengan python 3. Dalam python 2 byteshanyalah alias untuk str, yang artinya bytes([3])memberi Anda '[3]'.
botchniaque
8
Dalam Python 3, perhatikan bahwa bytes([n])hanya berfungsi untuk int n dari 0 hingga 255. Untuk hal lain yang dimunculkan ValueError.
Acumenus
8
@ABB: Tidak terlalu mengejutkan karena byte hanya dapat menyimpan nilai antara 0 dan 255.
Tim Pietzcker
7
Perlu juga dicatat bahwa bytes([3])masih berbeda dari apa yang diinginkan OP - yaitu nilai byte yang digunakan untuk mengkodekan angka "3" dalam ASCII, yaitu. bytes([51]), yang b'3'tidak b'\x03'.
lenz
2
bytes(500)membuat bytestring w / len == 500. Itu tidak membuat bytestring yang mengkodekan integer 500. Dan saya setuju itu bytes([500])tidak bisa bekerja, itulah sebabnya itu juga jawaban yang salah. Mungkin jawaban yang tepat adalah int.to_bytes()untuk versi> = 3.1.
weberc2
199

Dari python 3.2 bisa Anda lakukan

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

Dengan demikian x == int_from_bytes(int_to_bytes(x)),. Perhatikan bahwa pengodean ini hanya berfungsi untuk bilangan bulat (non-negatif) yang tidak ditandatangani.

berambut cokelat
sumber
4
Meskipun jawaban ini baik, ia hanya berfungsi untuk bilangan bulat (non-negatif) yang tidak ditandatangani. Saya telah mengadaptasi itu menulis jawaban yang juga berfungsi untuk bilangan bulat yang ditandatangani.
Acumenus
1
Itu tidak membantu untuk mendapatkan b"3"dari 3, seperti pertanyaannya. (Ini akan memberi b"\x03".)
gsnedders
40

Anda dapat menggunakan paket struct :

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

">" Adalah byte-order (big-endian) dan "I" adalah karakter format . Jadi Anda bisa spesifik jika Anda ingin melakukan sesuatu yang lain:

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

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

Ini berfungsi sama pada kedua python 2 dan python 3 .

Catatan: operasi terbalik (byte ke int) dapat dilakukan dengan membongkar .

Andy Hayden
sumber
2
@AndyHayden Untuk memperjelas, karena struct memiliki ukuran standar terlepas dari input, I, H, dan Bbekerja sampai 2**k - 1di mana k adalah 32, 16, dan 8 masing-masing. Untuk input yang lebih besar, mereka meningkatkan struct.error.
Acumenus
Agaknya turun karena tidak menjawab pertanyaan: OP ingin tahu bagaimana menghasilkan b'3\r\n', yaitu byte-string yang berisi karakter ASCII "3" bukan karakter ASCII "\ x03"
Dave Jones
1
@DaveJones Apa yang membuat Anda berpikir itulah yang diinginkan OP? The jawaban yang diterima kembali \x03, dan solusi jika Anda hanya ingin b'3'sepele. Alasan yang dikutip oleh ABB jauh lebih masuk akal ... atau setidaknya bisa dimengerti.
Andy Hayden
@DaveJones Juga, alasan saya menambahkan jawaban ini adalah karena Google membawa Anda ke sini ketika mencari untuk melakukan ini dengan tepat. Jadi itu sebabnya ada di sini.
Andy Hayden
4
Ini tidak hanya berfungsi sama dalam 2 dan 3, tetapi lebih cepat dari kedua metode bytes([x])dan (x).to_bytes()di Python 3.5. Itu tidak terduga.
Mark Ransom
25

Python 3.5+ memperkenalkan% -interpolasi (-style printfformat) untuk byte :

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

Lihat PEP 0461 - Menambahkan format% ke byte dan bytearray .

Pada versi sebelumnya, Anda dapat menggunakan strdan .encode('ascii')hasilnya:

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

Catatan: Ini berbeda dari apa yang int.to_bytesmenghasilkan :

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

Dokumentasi mengatakan:

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

Urutannya:

b'3\r\n'

Ini adalah karakter '3' (desimal 51) karakter '\ r' (13) dan '\ n' (10).

Oleh karena itu, cara akan memperlakukannya seperti itu, misalnya:

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

Diuji pada IPython 1.1.0 & Python 3.2.3

Schcriher
sumber
1
Saya akhirnya melakukan bytes(str(n), 'ascii') + b'\r\n'atau str(n).encode('ascii') + b'\r\n'. Terima kasih! :)
astrojuanlu
1
@ Juanlu001, juga "{}\r\n".format(n).encode()saya tidak berpikir ada salahnya dilakukan dengan menggunakan pengkodean utf8 default
John La Rooy
6

ASCIIfikasi 3 "\x33"tidak "\x03"!

Itulah yang python lakukan untuk str(3)tetapi akan benar-benar salah untuk byte, karena mereka harus dianggap array data biner dan tidak disalahgunakan sebagai string.

Cara paling mudah untuk mencapai apa yang Anda inginkan adalah bytes((3,)), yang lebih baik daripada bytes([3])karena menginisialisasi daftar jauh lebih mahal, jadi jangan pernah menggunakan daftar ketika Anda bisa menggunakan tuple. Anda dapat mengonversi bilangan bulat yang lebih besar dengan menggunakan int.to_bytes(3, "little").

Menginisialisasi byte dengan panjang yang diberikan masuk akal dan merupakan yang paling berguna, karena mereka sering digunakan untuk membuat beberapa jenis buffer yang memerlukan memori yang dialokasikan untuk ukuran tertentu. Saya sering menggunakan ini ketika menginisialisasi array atau memperluas beberapa file dengan menulis nol padanya.

Bachsau
sumber
1
Ada beberapa masalah dengan jawaban ini: (a) Notasi melarikan diri b'3'adalah b'\x33', tidak b'\x32'. (B) (3)bukan tupel - Anda harus menambahkan koma. (C) Skenario inisialisasi urutan dengan nol tidak berlaku untuk bytesobjek, karena mereka tidak berubah (masuk akal untuk bytearrays, meskipun).
lenz
Terima kasih atas komentar Anda. Saya memperbaiki dua kesalahan yang jelas itu. Dalam kasus bytesdan bytearray, saya pikir sebagian besar masalah konsistensi. Tetapi ini juga berguna jika Anda ingin mendorong beberapa angka nol ke dalam buffer atau file, dalam hal ini hanya digunakan sebagai sumber data.
Bachsau
5

int(termasuk Python2 long) dapat dikonversi bytesmenggunakan fungsi berikut:

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

Konversi terbalik dapat dilakukan oleh yang lain:

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)

Kedua fungsi bekerja pada Python2 dan Python3.

renskiy
sumber
'hex_value ='% x '% i' tidak akan berfungsi di bawah Python 3.4. Anda mendapatkan TypeError, jadi Anda harus menggunakan hex () sebagai gantinya.
bjmc
@ bjmc diganti dengan str.format. Ini harus bekerja pada Python 2.6+.
renskiy
Terima kasih, @renskiy. Anda mungkin ingin menggunakan 'hex_codec' alih-alih 'hex' karena sepertinya 'hex' alias tidak tersedia di semua rilis Python 3, lihat stackoverflow.com/a/12917604/845210
bjmc
@ bjmc diperbaiki. Terima kasih
renskiy
Ini gagal pada bilangan bulat negatif pada python 3.6
Berserker
4

Saya ingin tahu tentang kinerja berbagai metode untuk satu int dalam kisaran [0, 255], jadi saya memutuskan untuk melakukan beberapa tes waktu.

Berdasarkan timing bawah, dan dari kecenderungan umum saya amati dari mencoba banyak nilai dan konfigurasi yang berbeda, struct.packtampaknya menjadi yang tercepat, diikuti oleh int.to_bytes, bytesdan dengan str.encode(mengejutkan) menjadi yang paling lambat. Perhatikan bahwa hasilnya menunjukkan lebih banyak variasi daripada yang diwakili, dan int.to_bytesdan byteskadang - kadang beralih peringkat kecepatan selama pengujian, tetapi struct.packjelas yang tercepat.

Hasil dalam CPython 3.7 di 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

Modul uji (bernama 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
sumber
1
@ ABB Seperti yang disebutkan dalam kalimat pertama saya, saya hanya mengukur ini untuk satu int di kisaran [0, 255]. Saya berasumsi dengan "indikator yang salah" yang Anda maksud pengukuran saya tidak cukup umum untuk memenuhi sebagian besar situasi? Atau apakah metodologi pengukuran saya buruk? Jika yang terakhir, saya akan tertarik untuk mendengar apa yang Anda katakan, tetapi jika yang pertama, saya tidak pernah mengklaim pengukuran saya generik untuk semua kasus penggunaan. Untuk situasi saya (mungkin niche), saya hanya berurusan dengan int dalam kisaran [0, 255], dan itu adalah audiens yang saya maksudkan dengan jawaban ini. Apakah jawaban saya tidak jelas? Saya dapat mengeditnya untuk kejelasan ...
Graham
1
Bagaimana dengan teknik pengindeksan hanya pengkodean rentang untuk pengindeksan? Pra-perhitungan tidak akan tergantung pada waktu, hanya pengindeksan yang akan dilakukan.
Acumenus
@ ABB Itu ide yang bagus. Kedengarannya akan lebih cepat dari yang lainnya. Saya akan melakukan beberapa waktu dan menambahkannya ke jawaban ini ketika saya punya waktu.
Graham
3
Jika Anda benar-benar ingin mengatur waktu byte-dari-iterable, Anda harus menggunakan bytes((i,))alih-alih bytes([i])karena daftar lebih kompleks, menggunakan lebih banyak memori dan butuh waktu lama untuk menginisialisasi. Dalam hal ini, untuk apa-apa.
Bachsau
4

Meskipun jawaban sebelumnya oleh brunsgaard adalah pengkodean yang efisien, ia hanya berfungsi untuk bilangan bulat yang tidak ditandai. Yang ini dibangun untuk bekerja untuk bilangan bulat yang ditandatangani dan tidak ditandatangani.

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)

Untuk encoder, (i + ((i * signed) < 0)).bit_length()digunakan bukan hanya i.bit_length()karena yang terakhir mengarah pada pengkodean -128, -32768 yang tidak efisien, dll.


Kredit: CervEd untuk memperbaiki inefisiensi kecil.

Acumenus
sumber
int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)adalahFalse
CervEd
Anda tidak menggunakan panjang 2, Anda menghitung panjang bit dari integer yang ditandatangani, menambahkan 7, dan kemudian 1, jika itu adalah integer yang ditandatangani. Akhirnya Anda mengubahnya menjadi panjang dalam byte. Ini menghasilkan hasil yang tidak terduga -128, -32768dll.
CervEd
Ini adalah bagaimana Anda memperbaikinya(i+(signed*i<0)).bit_length()
CervEd
3

Perilaku ini berasal dari fakta bahwa dalam Python sebelum versi 3 byteshanyalah alias untuk str. Dalam Python3.x bytesadalah versi abadi bytearray- tipe yang sama sekali baru, tidak kompatibel.

aneh
sumber
3

Dari byte dokumen :

Dengan demikian, argumen konstruktor ditafsirkan sebagai bytearray ().

Kemudian, dari bytearray docs :

Parameter sumber opsional dapat digunakan untuk menginisialisasi array dengan beberapa cara berbeda:

  • Jika bilangan bulat, array akan memiliki ukuran itu dan akan diinisialisasi dengan byte nol.

Perhatikan, itu berbeda dari perilaku 2.x (di mana x> = 6), di mana byteshanya str:

>>> bytes is str
True

PEP 3112 :

2.6 str berbeda dari tipe byte 3.0 dalam berbagai cara; terutama, konstruktornya benar-benar berbeda.

alko
sumber
0

Beberapa jawaban tidak bekerja dengan jumlah besar.

Konversikan integer ke representasi hex, lalu konversikan ke byte:

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

Hasil:

>>> 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
sumber
1
"Semua metode lain tidak berfungsi dengan jumlah besar." Itu tidak benar, int.to_bytesbekerja dengan bilangan bulat apa pun.
juanpa.arrivillaga
@ juanpa.arrivillaga ya, salahku. Saya sudah mengedit jawaban saya.
Max Malysh
-1

Jika pertanyaannya adalah bagaimana mengubah bilangan bulat itu sendiri (bukan string yang setara) menjadi byte, saya pikir jawabannya adalah:

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

Informasi lebih lanjut tentang metode ini di sini:

  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
sumber
1
Bagaimana ini berbeda dari jawaban brunsgaard, diposting 5 tahun yang lalu dan saat ini jawaban dengan suara terbanyak?
Arthur Tacca