【问题标题】:Iterate over individual bytes in Python 3在 Python 3 中迭代单个字节
【发布时间】:2013-01-10 21:20:01
【问题描述】:

在 Python 3 中迭代 bytes 对象时,将单个 bytes 获取为 ints

>>> [b for b in b'123']
[49, 50, 51]

如何获取长度为 1 的 bytes 对象?

以下是可能的,但对读者来说不是很明显,很可能表现不佳:

>>> [bytes([b]) for b in b'123']
[b'1', b'2', b'3']

【问题讨论】:

  • 我想知道数组对象是否更适合您的目的并避免不必要的转换。
  • 表现相同,或者你是什么意思? >>>[b for b in bytearray(b"123")][49, 50, 51]
  • 我不相信 python 中有一个独特的“字符”类型。如果您查看数组模块的文档,您会看到 python 中的“字符”是 1 字节整数。所以你看到的结果是一致的。但是,我建议使用数组(没有完全了解您的应用程序)来建议它可以避免使用列表时可能发生的不必要的类型转换和对象构造。我怀疑即使是字符串也会导致额外的工作,但我不确定。正如其他人所指出的,您可以使用索引来提取您需要的项目。
  • 你说的数组是指bytearray吗?
  • 有人知道 Python3 为什么返回整数吗?我个人更喜欢 Python2 的行为。

标签: python python-3.x


【解决方案1】:

如果您担心此代码的性能,并且 int 作为字节不适合您的情况,那么您可能应该重新考虑您使用的数据结构,例如,改用 str 对象。

您可以对 bytes 对象进行切片以获得长度为 1 的 bytes 对象:

L = [bytes_obj[i:i+1] for i in range(len(bytes_obj))]

PEP 0467 -- Minor API improvements for binary sequences提出bytes.iterbytes()方法:

>>> list(b'123'.iterbytes())
[b'1', b'2', b'3']

【讨论】:

  • @flyingsheep:还有其他解决方案,具体取决于您的具体需求,例如L = list(memoryview(bytes_obj))
  • 再次创建整数列表
  • @flyingsheep:我当然知道这看起来不像 Python 风格。然而,Python 3 的设计在处理字节时会产生一定的尴尬,所以如果你真的必须坚持字节,这确实可能是最惯用的形式。
  • @PavelŠimerda:有 pep 467 可以改进这个特定的用例,也就是说,John Y 并不是唯一一个认为可以改进字节的 Python 3 API 的人。
  • iterbytes 从 python 3.8 开始似乎不起作用
【解决方案2】:

int.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']

与其他一些答案一样,目前尚不清楚这是否比 OP 的原始解决方案更具可读性:我认为长度和字节顺序参数使其更加嘈杂。

struct.unpack

另一种方法是使用struct.unpack,尽管这也可能被认为难以阅读,除非您熟悉 struct 模块:

>>> import struct
>>> struct.unpack('3c', b'123')
(b'1', b'2', b'3')

(正如 jfs 在 cmets 中观察到的,struct.unpack 的格式字符串可以动态构造;在这种情况下,我们知道结果中的单个字节数必须等于原始字节串中的字节数,所以 @987654327 @ 是可能的。)

性能

>>> 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 的性能比大多数“显而易见”的方法都要差。

【讨论】:

  • 不错的答案。它绝对值得赏金。
  • @Leon FWIW 我认为你的回答是最pythonic的;我想赏金的目的地将取决于赏金者是否想要可读性或性能:)(或出现更多、更好的答案)。
【解决方案3】:

我认为比较不同方法的运行时可能很有用,所以我做了一个基准测试(使用我的库 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 数组在这里? a bytes_obj 已经是一个可迭代的(超过ints))。如果可以接受可迭代(超过bytes)作为解决方案,那么您的基准测试表明snakecharmerb_struct 是最快的(尽管它复制字节,但它不会“迭代”)。基准测试表明,bytes_obj[i:i+1] 变体是迭代单个字节的解决方案中最快的。
  • @jfs 是的,没错。 NumPy 和 struct 解决方案仅将可迭代对象表示为字节,它们不会对其进行迭代。然而,这些解决方案收集了一些赞成票,因此将它们排除在外是不公平的,但也许我应该更详细地讨论这些差异。也许我会在接下来的几天里找到时间来修改答案。谢谢。
【解决方案4】:

从 python 3.5 开始你可以使用% formatting to bytes and bytearray:

[b'%c' % i for i in b'123']

输出:

[b'1', b'2', b'3']

上述解决方案比您最初的方法快 2-3 倍,如果您想要更快的解决方案,我建议使用numpy.frombuffer

import numpy as np
np.frombuffer(b'123', dtype='S1')

输出:

array([b'1', b'2', b'3'], 
      dtype='|S1')

第二种解决方案比 struct.unpack 快约 10%(我使用了与 @snakecharmerb 相同的性能测试,针对 100 个随机字节)

【讨论】:

    【解决方案5】:

    map()bytes()zip() 的三重奏可以解决问题:

    >>> list(map(bytes, zip(b'123')))
    [b'1', b'2', b'3']
    

    但是我不认为它比[bytes([b]) for b in b'123'] 更具可读性或性能更好。

    【讨论】:

      【解决方案6】:

      我使用这个辅助方法:

      def iter_bytes(my_bytes):
          for i in range(len(my_bytes)):
              yield my_bytes[i:i+1]
      

      适用于 Python2 和 Python3。

      【讨论】:

        【解决方案7】:

        一个简单的方法:

        [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']
        猜你喜欢
        • 2011-11-21
        • 2011-04-06
        • 2018-01-08
        • 2019-01-25
        • 1970-01-01
        • 1970-01-01
        • 2017-02-12
        • 2014-04-04
        • 2019-07-31
        相关资源
        最近更新 更多