为什么 "bytes(n)" 会创建一个长度为 n 字节的字符串,而不是将 n 转换为二进制表示?

280

我试图在Python 3中构建这个字节对象:

b'3\r\n'

所以我尝试了显而易见的方法(对我来说),但发现了奇怪的行为:

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

显然:

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

阅读文档后,我无法看到有关字节转换如何起作用的任何指针。但是,在这个Python问题中,我发现了一些意外的消息,涉及将format添加到bytes中(请参见Python 3 bytes formatting):

http://bugs.python.org/issue3982

这甚至会与像bytes(int)现在返回零之类的奇怪情况更加不兼容

和:

如果bytes(int)返回该int的ASCIIfication,那对我来说将更加方便;但是,老实说,即使出现错误,也比这种行为更好。(如果我想要这种行为-我从来没有过-我宁愿它是一个classmethod,像“bytes.zeroes(n)”一样调用。)

有人能向我解释这种行为来自哪里吗?


5
根据你的问题不清楚你想要整数值3还是代表数字3的ASCII字符值(整数值为51)。前者为bytes([3]) == b'\x03'。后者为bytes([ord('3')]) == b'3'。 - florisla
1
("3" + "\r\n").encode()有什么问题? - GLRoman
16个回答

346

从Python 3.2开始,您可以使用to_bytes方法:

>>> (1024).to_bytes(2, byteorder='big')
b'\x04\x00'
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')

因此,x == int_from_bytes(int_to_bytes(x))

请注意,上述编码仅适用于无符号(非负)整数。

对于有符号整数,位长度的计算略微棘手:

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)

4
这个回答虽然不错,但仅适用于无符号(非负)整数。我已经改编它并撰写了一个答案,该答案也适用于有符号整数。 - Asclepius
4
这对于从数字 3 得到字节串 b"3" 并没有帮助,正如问题所询问的那样。(这会给出 b"\x03"。) - gsnedders
1
值得一提的是,to_bytesfrom_bytes都支持signed参数。这允许存储正数和负数,但代价是多一个比特位。 - MisterMiyagi
(https://dev59.com/H73pa4cB1Zd3GeqPmtsT#64502258 解释了 +7 的作用。) - user202729
为什么需要括号,我在哪里可以找到相关文档? - young_souvlaki
不知道还有 X.to_bytes(2, byteorder='big') 这个方法,而且你还可以设置字节顺序!非常感谢! - boardkeystown

255

这就是它的设计方式-这是有道理的,因为通常情况下,您会在可迭代对象上调用bytes而不是单个整数:

这是它的设计原则 - 这么做是有意义的,因为通常情况下,您会在可迭代对象上调用bytes而不是单个整数:

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

文档和bytes的docstring都表明了这一点:docs state this

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

30
请注意,上述内容仅适用于Python 3。 在Python 2中,bytes仅是str的别名,这意味着bytes([3])会给出'[3]' - botchniaque
20
请注意,在Python 3中,bytes([n])仅适用于整数n的范围为0到255。对于其他任何值,它都会引发ValueError异常。 - Asclepius
11
并不令人意外,因为一个字节只能存储0到255之间的值。 - Tim Pietzcker
9
需要注意的是,bytes([3]) 与原帖所需的内容仍然不同 - 即用于编码ASCII中数字“3”的字节值,即bytes([51]),它是b'3'而不是b'\x03' - lenz
2
bytes(500) 创建一个长度为500的字节串。它不会创建一个编码整数500的字节串。我同意bytes([500])是行不通的,这也是为什么那是错误的答案。可能正确的答案是在版本>=3.1中使用int.to_bytes() - weberc2
显示剩余4条评论

51
你可以使用struct模块的pack函数
In [11]: struct.pack(">I", 1)
Out[11]: '\x00\x00\x00\x01'

">" 是 字节顺序(大端),而 "I" 则是格式字符。因此,如果您想要执行其他操作,可以具体说明:

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

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

这在Python 2和Python 3上都是相同的。

注意:反向操作(字节到整数)可以使用unpack完成。


2
@AndyHayden 为了澄清,由于结构体的大小与输入无关,因此 IHB 可以处理 2 ** k - 1,其中 k 分别为 32、16 和 8。对于更大的输入,它们会引发 struct.error - Asclepius
可能因为它没有回答问题而被投下反对票:OP想知道如何生成b'3\r\n',即包含ASCII字符“3”的字节字符串,而不是ASCII字符“\x03”。 - Dave Jones
2
@DaveJones 你为什么认为这就是 OP 想要的呢?被接受的答案返回 \x03,如果你只想要 b'3' 的话,解决方案是微不足道的。A-B-B 提到的原因更加合理...或者至少更容易理解。 - Andy Hayden
@DaveJones 此外,我添加这个答案的原因是因为当你搜索要做这个的时候,谷歌会将你带到这里。所以这就是它在这里的原因。 - Andy Hayden
5
这个方法不仅在 Python 2 和 3 中都适用,而且比 Python 3.5 中的 bytes([x])(x).to_bytes() 方法更快。这是出乎意料的。 - Mark Ransom

32

13

文档说明:

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

这个序列:

b'3\r\n'

这是字符'3' (十进制51)、字符'\r' (13) 和 '\n' (10)。

因此,处理它的方式就是这样,例如:

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

在IPython 1.1.0和Python 3.2.3上进行测试


1
我最终使用了bytes(str(n), 'ascii') + b'\r\n'或者str(n).encode('ascii') + b'\r\n'。谢谢! :) - astrojuanlu
1
@Juanlu001,还有"{}\r\n".format(n).encode(),我认为使用默认的utf8编码不会造成任何损害。 - John La Rooy

8

数字3的ASCII码表示为"\x33",而不是"\x03"

这是Python中str(3)的输出结果,但对于bytes来说却是完全错误的。因为bytes应该被视为二进制数据的数组,而不应该被滥用成字符串。

实现你想要的最简单方法是使用bytes((3,))。这比bytes([3])更好,因为初始化列表的代价更高,所以尽可能使用元组而非列表。可以使用int.to_bytes(3, "little")将更大的整数转换为bytes。

在给定长度的情况下初始化bytes变量是有意义且最有用的。因为它们通常用于创建某种类型的缓冲区,在该缓冲区中需要分配一些给定大小的内存。我通常在初始化数组或通过写零字符来扩展某个文件时使用它们。


1
这个答案有几个问题:(a) b'3'的转义符号是b'\x33',而不是b'\x32'。(b)(3)不是元组——你必须添加一个逗号。(c)用零初始化序列的情况不适用于bytes对象,因为它们是不可变的(虽然对于bytearray是有意义的)。 - lenz
感谢您的评论。我已经修复了那两个明显的错误。在bytesbytearray的情况下,我认为这主要是一致性的问题。但如果您想将一些零推入缓冲区或文件,则它也非常有用,此时它仅用作数据源。 - Bachsau

5

我对在范围[0, 255]内的单个整数的各种方法的性能很感兴趣,因此我决定进行一些计时测试。

根据以下的计时结果和我从尝试许多不同的值和配置中观察到的一般趋势,struct.pack似乎是最快的,其次是int.to_bytesbytes,而str.encode(毫不奇怪)是最慢的。请注意,结果显示了比所表示的更多的变化,int.to_bytesbytes有时在测试期间会交换速度排名,但struct.pack显然是最快的。

在Windows上的CPython 3.7中的结果:

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

测试模块(命名为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://dev59.com/SWEi5IYBdhLWcg3wueN0#21017834
    """
    return bytes([i])

def to_bytes(i):
    """From brunsgaard's answer:
    https://dev59.com/SWEi5IYBdhLWcg3wueN0#30375198
    """
    return i.to_bytes(1, byteorder='big')

def struct_pack(i):
    """From Andy Hayden's answer:
    https://dev59.com/SWEi5IYBdhLWcg3wueN0#26920966
    """
    return struct.pack('B', i)

# Originally, jfs's answer was considered for testing,
# but the result is not identical to the other methods
# https://dev59.com/SWEi5IYBdhLWcg3wueN0#31761722

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://dev59.com/wGIk5IYBdhLWcg3wrv24
        timeit.main(args=shlex.split(
            f"-s 'from int_to_byte import {c.__name__}; value = {value}' " +
            f"'{c.__name__}(value)'"
        ))

1
如我在第一句中提到的,我只测量了范围为 [0, 255] 的单个整数。你所说的“错误指标”是指我的测量不够普遍适用于大多数情况吗?还是我的测量方法不好?如果是后者,我很想听听你的意见,但如果是前者,我从未声称我的测量适用于所有用例。对于我的(也许是小众的)情况,我只处理范围在 [0, 255] 的整数,并且这是我打算回答的受众。我的回答是否不清楚?我可以编辑它以使其更加明确... - Graham
1
那么仅索引预计算范围的编码技术怎么样?预计算不会受到时间影响,只有索引会受到。 - Asclepius
@A-B-B 那是个好主意。听起来比其他任何方法都要快。我会计时并在有时间时将其添加到此答案中。 - Graham
3
如果你真的想计时从可迭代对象生成 bytes 的过程,你应该使用bytes((i,))而不是bytes([i]),因为列表更复杂、占用更多内存,并且初始化时间更长。 在这种情况下,这样做没有任何意义。 - Bachsau

5

尽管之前的 brunsgaard所提供的答案是一种高效的编码方式,但它只适用于无符号整数。这个方法在此基础上进行改进,可以同时适用于有符号和无符号整数。

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 + ((i * signed) < 0)).bit_length()而不是仅仅使用i.bit_length(),因为后者会导致-128、-32768等数字的编码效率低下。

感谢CervEd修复了一个小的低效问题。


int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True) is False - CervEd
你没有使用长度2,而是计算有符号整数的位长度,加上7,如果是有符号整数,则再加1。最后将其转换为字节长度。这会导致-128-32768等意外的结果。 - CervEd
让我们在聊天中继续这个讨论 - CervEd
这是修复它的方法:(i+(signed*i<0)).bit_length() - CervEd

4

int(包括Python2的long)可以使用以下函数转换为bytes

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

另一个方法可以进行反向转换:
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)

这两个函数适用于Python2和Python3。


'hex_value = '%x' % i' 在 Python 3.4 中无法工作。您会收到 TypeError 错误,因此必须改用 hex()。 - bjmc
使用str.format替换@bjmc。这应该适用于Python 2.6+。 - renskiy
谢谢,@renskiy。您可能想使用'hex_codec'而不是'hex',因为似乎并非所有Python 3版本都可用'hex'别名,请参见https://dev59.com/2Wcs5IYBdhLWcg3wXSuU#12917604 - bjmc
@bjmc已修复。谢谢。 - renskiy
在Python 3.6上,这会在负整数上失败。 - Berserker

4

根据bytes文档:

因此,构造函数参数的解释方式与bytearray()相同。

然后,从bytearray文档中:

可选的source参数可用于以几种不同的方式初始化数组:

  • 如果它是一个整数,则数组将具有该大小,并将用空字节初始化。

请注意,这与2.x (其中x >= 6)的行为不同,其中 bytes 只是 str

>>> bytes is str
True

PEP 3112

2.6版本的str类型与3.0版本的bytes类型有很多不同之处,最显著的是构造函数完全不同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接