Chuyển đổi int thành byte trong Python 3


176

Tôi đã cố gắng xây dựng đối tượng byte này trong Python 3:

b'3\r\n'

Vì vậy, tôi đã thử điều hiển nhiên (đối với tôi) và tìm thấy một hành vi kỳ lạ:

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

Rõ ràng:

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

Tôi đã không thể thấy bất kỳ con trỏ nào về lý do tại sao chuyển đổi byte hoạt động theo cách này đọc tài liệu. Tuy nhiên, tôi đã tìm thấy một số thông báo bất ngờ trong vấn đề Python này về việc thêm formatvào byte (xem thêm định dạng Python 3 byte ):

http://bugs.python.org/su3982

Điều này tương tác thậm chí còn kém hơn với các số lẻ như byte (int) trả về số 0 ngay bây giờ

và:

Sẽ thuận tiện hơn nhiều cho tôi nếu byte (int) trả về ASCIIfication của int đó; nhưng thành thật mà nói, thậm chí một lỗi sẽ tốt hơn hành vi này. (Nếu tôi muốn hành vi này - điều mà tôi không bao giờ có - tôi muốn nó là một đối tượng phân loại, được gọi như "byte.zeroes (n)".)

Ai đó có thể giải thích cho tôi hành vi này đến từ đâu?


1
liên quan đến tiêu đề:3 .to_bytes
jfs

2
Không rõ ràng từ câu hỏi của bạn nếu bạn muốn giá trị số nguyên 3 hoặc giá trị của ký tự ASCII đại diện cho số ba (giá trị số nguyên 51). Đầu tiên là byte ([3]) == b '\ x03'. Cái sau là byte ([ord ('3')]) == b'3 '.
florisla

Câu trả lời:


176

Đó là cách nó được thiết kế - và nó có ý nghĩa bởi vì thông thường, bạn sẽ gọi bytesmột số lặp thay vì một số nguyên duy nhất:

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

Các tài liệu nêu rõ điều này , cũng như chuỗi doc cho bytes:

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

25
Coi chừng những điều trên chỉ hoạt động với python 3. Trong python 2 byteschỉ là bí danh str, có nghĩa là bytes([3])mang lại cho bạn '[3]'.
botchniaque

8
Trong Python 3, lưu ý rằng bytes([n])chỉ hoạt động cho int n từ 0 đến 255. Đối với bất kỳ thứ gì khác, nó tăng lên ValueError.
Acumenus

8
@ABB: Không thực sự đáng ngạc nhiên vì một byte chỉ có thể lưu trữ các giá trị trong khoảng từ 0 đến 255.
Tim Pietzcker

7
Cũng cần lưu ý rằng bytes([3])vẫn khác với những gì OP muốn - cụ thể là giá trị byte được sử dụng để mã hóa chữ số "3" trong ASCII, nghĩa là. bytes([51]), đó là b'3', không b'\x03'.
lenz

2
bytes(500)tạo ra một bytestring w / len == 500. Nó không tạo ra một bytestring mã hóa số nguyên 500. Và tôi đồng ý rằng bytes([500])không thể hoạt động, đó là lý do tại sao đó cũng là câu trả lời sai. Có lẽ câu trả lời đúng là int.to_bytes()dành cho các phiên bản> = 3.1.
weberc2

197

Từ python 3.2 bạn có thể làm

>>> (1024).to_bytes(2, byteorder='big')
b'\x04\x00'

https://docs.python.org/3/l 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')

Theo đó , x == int_from_bytes(int_to_bytes(x)). Lưu ý rằng mã hóa này chỉ hoạt động đối với các số nguyên không dấu (không âm).


4
Trong khi câu trả lời này là tốt, nó chỉ hoạt động cho các số nguyên không dấu (không âm). Tôi đã điều chỉnh nó viết một câu trả lời cũng hoạt động cho các số nguyên đã ký.
Acumenus

1
Điều đó không giúp gì cho việc đi b"3"từ 3, như câu hỏi yêu cầu. (Nó sẽ cho b"\x03".)
gsnedder

40

Bạn có thể sử dụng gói struct :

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

">" Là thứ tự byte (big endian) và "I" là ký tự định dạng . Vì vậy, bạn có thể cụ thể nếu bạn muốn làm một cái gì đó khác:

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

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

Điều này hoạt động tương tự trên cả python 2 và python 3 .

Lưu ý: thao tác nghịch đảo (byte thành int) có thể được thực hiện với giải nén .


2
@AndyHayden Để làm rõ, kể từ khi một cấu trúc có kích thước tiêu chuẩn không phụ thuộc vào đầu vào, I, H, và Blàm việc cho đến 2**k - 1nơi k là 32, 16, và 8 tương ứng. Đối với đầu vào lớn hơn họ nâng cao struct.error.
Acumenus

Có lẽ đã bỏ phiếu vì nó không trả lời được câu hỏi: OP muốn biết cách tạo b'3\r\n', tức là một chuỗi byte chứa ký tự ASCII "3" chứ không phải ký tự ASCII "\ x03"
Dave Jones

1
@DaveJones Điều gì khiến bạn nghĩ đó là những gì OP muốn? Các câu trả lời được chấp nhận lợi nhuận \x03, và các giải pháp nếu bạn chỉ muốn b'3'là tầm thường. Lý do được ABB trích dẫn là hợp lý hơn nhiều ... hoặc ít nhất là dễ hiểu.
Andy Hayden

@DaveJones Ngoài ra, lý do tôi thêm câu trả lời này là vì Google đưa bạn đến đây khi tìm kiếm để thực hiện chính xác điều này. Vì vậy, đó là lý do tại sao nó ở đây.
Andy Hayden

4
Điều này không chỉ hoạt động giống nhau trong 2 và 3, mà còn nhanh hơn cả phương thức bytes([x])(x).to_bytes()phương thức trong Python 3.5. Đó là bất ngờ.
Đánh dấu tiền chuộc

25

Python 3.5+ giới thiệu% -interpolation ( printfđịnh dạng kiểu) cho byte :

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

Xem PEP 0461 - Thêm định dạng% vào byte và bytearray .

Trên các phiên bản trước, bạn có thể sử dụng str.encode('ascii')kết quả:

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

Lưu ý: Nó khác với những gì int.to_bytessản xuất :

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

11

Các tài liệu nói:

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

Trình tự:

b'3\r\n'

Đó là ký tự '3' (thập phân 51) ký tự '\ r' (13) và '\ n' (10).

Do đó, cách sẽ đối xử với nó như vậy, ví dụ:

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

Đã thử nghiệm trên IPython 1.1.0 & Python 3.2.3


1
Tôi đã kết thúc làm bytes(str(n), 'ascii') + b'\r\n'hoặc str(n).encode('ascii') + b'\r\n'. Cảm ơn! :)
astrojuanlu

1
@ Juanlu001, "{}\r\n".format(n).encode()tôi cũng không nghĩ có bất kỳ tác hại nào khi sử dụng mã hóa utf8 mặc định
John La Rooy

6

ASCIIfication của 3 thì "\x33"không "\x03"!

Đó là những gì python làm cho str(3)nhưng nó sẽ hoàn toàn sai đối với byte, vì chúng nên được coi là mảng dữ liệu nhị phân và không bị lạm dụng dưới dạng chuỗi.

Cách dễ nhất để đạt được những gì bạn muốn là bytes((3,))tốt hơn so với việc bytes([3])khởi tạo một danh sách đắt hơn nhiều, vì vậy đừng bao giờ sử dụng danh sách khi bạn có thể sử dụng bộ dữ liệu. Bạn có thể chuyển đổi số nguyên lớn hơn bằng cách sử dụng int.to_bytes(3, "little").

Khởi tạo byte với độ dài nhất định có ý nghĩa và hữu ích nhất, vì chúng thường được sử dụng để tạo một số loại bộ đệm mà bạn cần một số bộ nhớ có kích thước nhất định được phân bổ. Tôi thường sử dụng điều này khi khởi tạo mảng hoặc mở rộng một số tệp bằng cách viết số không vào nó.


1
Có một số vấn đề với câu trả lời này: (a) Ký hiệu thoát b'3'b'\x33'không b'\x32'. (b) (3)không phải là một tuple - bạn phải thêm dấu phẩy. (c) Kịch bản khởi tạo một chuỗi với các số 0 không áp dụng cho bytescác đối tượng, vì chúng là bất biến ( bytearraymặc dù nó có ý nghĩa đối với s).
lenz

Cám ơn bạn đã góp ý. Tôi đã sửa hai lỗi rõ ràng đó. Trong trường hợp bytesbytearray, tôi nghĩ đó chủ yếu là vấn đề nhất quán. Nhưng nó cũng hữu ích nếu bạn muốn đẩy một số không vào bộ đệm hoặc tệp, trong trường hợp đó, nó chỉ được sử dụng làm nguồn dữ liệu.
Bachsau

5

int(bao gồm cả Python2 long) có thể được chuyển đổi sang bytessử dụng chức năng sau:

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

Việc chuyển đổi ngược có thể được thực hiện bởi một số khác:

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)

Cả hai hàm đều hoạt động trên cả Python2 và Python3.


'hex_value ='% x '% i' sẽ không hoạt động trong Python 3.4. Bạn nhận được TypeError, vì vậy bạn phải sử dụng hex ().
bjmc

@bjmc thay thế bằng str.format. Điều này sẽ hoạt động trên Python 2.6+.
renskiy

Cảm ơn, @DRkiy. Bạn có thể muốn sử dụng 'hex_codec' thay vì 'hex' vì có vẻ như bí danh 'hex' không có sẵn trên tất cả các bản phát hành Python 3, xem stackoverflow.com/a/12917604/845210
bjmc

@bjmc đã sửa. Cảm ơn
renskiy

Điều này không thành công trên các số nguyên âm trên python 3.6
Berserker

4

Tôi tò mò về hiệu suất của các phương thức khác nhau cho một int trong phạm vi [0, 255], vì vậy tôi quyết định thực hiện một số thử nghiệm thời gian.

Dựa trên timings dưới đây, và từ xu hướng chung tôi quan sát thấy từ cố gắng nhiều giá trị và cấu hình khác nhau, struct.packcó vẻ là nhanh nhất, tiếp theo là int.to_bytes, bytesvà với str.encode(gì ngạc nhiên) là chậm nhất. Lưu ý rằng kết quả cho thấy một số biến thể nhiều hơn so với được trình bày int.to_bytesbytesđôi khi chuyển đổi thứ hạng tốc độ trong quá trình thử nghiệm, nhưng struct.packrõ ràng là nhanh nhất.

Kết quả trong CPython 3.7 trên 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

Mô-đun kiểm tra (có tên 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)'"
        ))

1
@ABB Như đã đề cập trong câu đầu tiên của tôi, tôi chỉ đo điều này cho một số nguyên trong phạm vi [0, 255]. Tôi giả sử bằng "chỉ số sai", ý bạn là số đo của tôi không đủ chung để phù hợp với hầu hết các tình huống? Hay phương pháp đo lường của tôi kém? Nếu sau này, tôi sẽ muốn nghe những gì bạn nói, nhưng nếu trước đây, tôi không bao giờ tuyên bố các phép đo của tôi là chung cho tất cả các trường hợp sử dụng. Đối với tình huống (có lẽ là thích hợp) của tôi, tôi chỉ xử lý các số nguyên trong phạm vi [0, 255]và đó là đối tượng mà tôi dự định sẽ giải quyết với câu trả lời này. Là câu trả lời của tôi không rõ ràng? Tôi có thể chỉnh sửa nó cho rõ ràng ...
Graham

1
Thế còn kỹ thuật chỉ lập chỉ mục mã hóa được tính toán trước cho phạm vi thì sao? Việc tính toán trước sẽ không tuân theo thời gian, chỉ có việc lập chỉ mục là được.
Acumenus

@ABB Đó là một ý tưởng tốt. Nghe có vẻ như nó sẽ nhanh hơn bất cứ thứ gì khác. Tôi sẽ thực hiện một số thời gian và thêm nó vào câu trả lời này khi tôi có thời gian.
Graham

3
Nếu bạn thực sự muốn tính thời gian cho thứ byte-từ-iterable, bạn nên sử dụng bytes((i,))thay bytes([i])vì vì danh sách phức tạp hơn, sử dụng nhiều bộ nhớ hơn và mất nhiều thời gian để khởi tạo. Trong trường hợp này, không có gì.
Bachsau

4

Mặc dù câu trả lời trước của brungaard là một mã hóa hiệu quả, nó chỉ hoạt động đối với các số nguyên không dấu. Cái này được xây dựng dựa trên nó để làm việc cho cả số nguyên có dấu và không dấu.

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)

Đối với bộ mã hóa, (i + ((i * signed) < 0)).bit_length()được sử dụng thay vì chỉ i.bit_length()vì bộ mã hóa dẫn đến mã hóa không hiệu quả là -128, -32768, v.v.


Tín dụng: CervEd để sửa lỗi không hiệu quả.


int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)False
Cổ tử cung

Bạn không sử dụng độ dài 2, bạn đang tính độ dài bit của số nguyên đã ký, thêm 7, rồi 1, nếu đó là số nguyên đã ký. Cuối cùng, bạn chuyển đổi nó thành độ dài tính bằng byte. Điều này mang lại kết quả bất ngờ cho -128, -32768v.v.
CervEd


Đây là cách bạn khắc phục nó(i+(signed*i<0)).bit_length()
CervEd

3

Hành vi này xuất phát từ thực tế là trong Python trước phiên bản 3 byteschỉ là bí danh str. Trong Python3.x byteslà một phiên bản bất biến của bytearray- loại hoàn toàn mới, không tương thích ngược.


3

Từ các tài liệu byte :

Theo đó, các đối số của hàm tạo được hiểu như đối với bytearray ().

Sau đó, từ tài liệu bytearray :

Tham số nguồn tùy chọn có thể được sử dụng để khởi tạo mảng theo một số cách khác nhau:

  • Nếu là số nguyên, mảng sẽ có kích thước đó và sẽ được khởi tạo với byte rỗng.

Lưu ý, khác với hành vi 2.x (trong đó x> = 6), trong đó bytesđơn giản là str:

>>> bytes is str
True

PEP 3112 :

2.6 str khác với loại byte 3.0 theo nhiều cách khác nhau; đáng chú ý nhất, các nhà xây dựng là hoàn toàn khác nhau.


0

Một số câu trả lời không hoạt động với số lượng lớn.

Chuyển đổi số nguyên thành biểu diễn hex, sau đó chuyển đổi nó thành byte:

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

Kết quả:

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

1
"Tất cả các phương pháp khác không hoạt động với số lượng lớn." Điều đó không đúng, int.to_byteshoạt động với bất kỳ số nguyên nào.
juanpa.arrivillaga

@ juanpa.arrivillaga vâng, xấu của tôi. Tôi đã chỉnh sửa câu trả lời của mình.
Max Malysh

-1

Nếu câu hỏi là làm thế nào để tự chuyển đổi một số nguyên (không phải chuỗi tương đương) thành byte, tôi nghĩ câu trả lời mạnh mẽ là:

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

Thông tin thêm về các phương pháp này ở đây:

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

1
Điều này khác với câu trả lời của brungaard, được đăng 5 năm trước và hiện là câu trả lời được bình chọn cao nhất?
Arthur Tacca
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.