在Python 3中迭代bytes
对象时,会将每个bytes
作为int
返回:
>>> [b for b in b'123']
[49, 50, 51]
如何获得长度为1的 bytes
对象呢?
以下方法可能可行,但对读者来说并不是很明显,并且可能性能不佳:
```python b = b'x'[0:1] ```
>>> [bytes([b]) for b in b'123']
[b'1', b'2', b'3']
在Python 3中迭代bytes
对象时,会将每个bytes
作为int
返回:
>>> [b for b in b'123']
[49, 50, 51]
如何获得长度为1的 bytes
对象呢?
以下方法可能可行,但对读者来说并不是很明显,并且可能性能不佳:
>>> [bytes([b]) for b in b'123']
[b'1', b'2', b'3']
如果您关注此代码的性能并且将int
作为字节不是适合您的接口,则应该重新考虑您使用的数据结构,例如改用str
对象。
您可以切片bytes
对象以获取1长度的bytes
对象:
L = [bytes_obj[i:i+1] for i in range(len(bytes_obj))]
有一个PEP 0467 -- 二进制序列的轻微API改进建议引入bytes.iterbytes()
方法:
>>> list(b'123'.iterbytes())
[b'1', b'2', b'3']
L = list(memoryview(bytes_obj))
。 - jfsiterbytes
似乎无法正常工作。 - Lord Elronditerbytes
”这个PEP已推迟到Python 3.9,因为目前尚未解决的问题预计不会在2019年5月Python 3.8功能添加截止日期之前得到解决(如果您热衷于看到这些更改得以实施并愿意推动解决过程,请联系PEP作者)。 - jfsint.to_bytes
int
对象有一个 to_bytes 方法,可用于将 int 转换为其对应的字节表示:
>>> import sys
>>> [i.to_bytes(1, sys.byteorder) for i in b'123']
[b'1', b'2', b'3']
和其他一些答案一样,不清楚这是否比原来的解决方案更易读:我认为长度和字节顺序参数使它变得更加混乱。
struct.unpack
另一种方法是使用struct.unpack,但除非您熟悉结构模块,否则可能也难以阅读:
>>> import struct
>>> struct.unpack('3c', b'123')
(b'1', b'2', b'3')
(正如评论中jfs所观察到的,可以动态构建struct.unpack
的格式字符串;在这种情况下,我们知道结果中单个字节的数量必须等于原始字节串中的字节数,因此struct.unpack(str(len(bytestring)) + 'c', bytestring)
是可能的。)
性能
>>> import random, timeit
>>> bs = bytes(random.randint(0, 255) for i in range(100))
>>> # OP's solution
>>> timeit.timeit(setup="from __main__ import bs",
stmt="[bytes([b]) for b in bs]")
46.49886950897053
>>> # Accepted answer from jfs
>>> timeit.timeit(setup="from __main__ import bs",
stmt="[bs[i:i+1] for i in range(len(bs))]")
20.91463226894848
>>> # Leon's answer
>>> timeit.timeit(setup="from __main__ import bs",
stmt="list(map(bytes, zip(bs)))")
27.476876026019454
>>> # guettli's answer
>>> timeit.timeit(setup="from __main__ import iter_bytes, bs",
stmt="list(iter_bytes(bs))")
24.107485140906647
>>> # user38's answer (with Leon's suggested fix)
>>> timeit.timeit(setup="from __main__ import bs",
stmt="[chr(i).encode('latin-1') for i in bs]")
45.937552741961554
>>> # Using int.to_bytes
>>> timeit.timeit(setup="from __main__ import bs;from sys import byteorder",
stmt="[x.to_bytes(1, byteorder) for x in bs]")
32.197659170022234
>>> # Using struct.unpack, converting the resulting tuple to list
>>> # to be fair to other methods
>>> timeit.timeit(setup="from __main__ import bs;from struct import unpack",
stmt="list(unpack('100c', bs))")
1.902243083808571
struct.unpack
似乎比其他方法快至少一个数量级,可能是因为它在字节级别上运作。 另一方面,int.to_bytes
的表现比大多数“显而易见”的方法要差。
我认为比较不同方法的运行时间可能会很有用,因此我进行了基准测试(使用我的库simple_benchmark
):
对于大字节对象,NumPy解决方案毫无疑问是最快的解决方案。
但如果需要一个结果列表,则NumPy解决方案(带有tolist()
)和struct
解决方案都比其他替代方案快得多。
我没有包括guettlis的答案,因为它与jfs的解决方案几乎相同,只是使用生成器函数代替了推导式。
import numpy as np
import struct
import sys
from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()
@b.add_function()
def jfs(bytes_obj):
return [bytes_obj[i:i+1] for i in range(len(bytes_obj))]
@b.add_function()
def snakecharmerb_tobytes(bytes_obj):
return [i.to_bytes(1, sys.byteorder) for i in bytes_obj]
@b.add_function()
def snakecharmerb_struct(bytes_obj):
return struct.unpack(str(len(bytes_obj)) + 'c', bytes_obj)
@b.add_function()
def Leon(bytes_obj):
return list(map(bytes, zip(bytes_obj)))
@b.add_function()
def rusu_ro1_format(bytes_obj):
return [b'%c' % i for i in bytes_obj]
@b.add_function()
def rusu_ro1_numpy(bytes_obj):
return np.frombuffer(bytes_obj, dtype='S1')
@b.add_function()
def rusu_ro1_numpy_tolist(bytes_obj):
return np.frombuffer(bytes_obj, dtype='S1').tolist()
@b.add_function()
def User38(bytes_obj):
return [chr(i).encode() for i in bytes_obj]
@b.add_arguments('byte object length')
def argument_provider():
for exp in range(2, 18):
size = 2**exp
yield size, b'a' * size
r = b.run()
r.plot()
rusu_ro1_numpy
实际上并没有“迭代单个字节”(基准测试显示它甚至不复制字节——时间是恒定的——为什么我们需要一个numpy数组?bytes_obj
已经是可迭代的(在int
上))。如果可接受一个(在bytes
上)的可迭代解决方案,那么您的基准测试表明snakecharmerb_struct
是最快的(尽管它复制了字节,但它不会“迭代”)。基准测试表明,在迭代单个字节的解决方案中,bytes_obj[i:i+1]
变体是最快的。 - jfs自Python 3.5开始,您可以使用%格式化为字节和字节数组:
[b'%c' % i for i in b'123']
输出:
[b'1', b'2', b'3']
如果你想要更快的解决方案,我建议使用numpy.frombuffer,上面的解决方案比你最初的方法快2-3倍:
import numpy as np
np.frombuffer(b'123', dtype='S1')
输出:
array([b'1', b'2', b'3'],
dtype='|S1')
第二种解决方案比struct.unpack快大约10%(我使用了与 @snakecharmerb 相同的性能测试,对100个随机字节进行测试)
def iter_bytes(my_bytes):
for i in range(len(my_bytes)):
yield my_bytes[i:i+1]
适用于Python2和Python3。
map()
, bytes()
和 zip()
函数即可完成操作:>>> list(map(bytes, zip(b'123')))
[b'1', b'2', b'3']
[bytes([b]) for b in b'123']
更易读,也没有更好的性能表现。
[bytes([i]) for i in b'123\xaa\xbb\xcc\xff']
bytes
对象包含128-255范围内的值,则无法正常工作。您需要使用latin-1
(与iso-8859-1
相同)编码来解决这个问题:[chr(i).encode('latin-1') for i in b'\x80\xb2\xff']
- Leon
>>>[b for b in bytearray(b"123")]
⇒[49, 50, 51]
- flying sheep