将变长字节数组转换为整数/长整数

56

如何将一个(大端)变长的二进制字节数组转换为(无符号)整数/长整数?例如,'\x11\x34' 表示 4404。

我目前正在使用:

def bytes_to_int(bytes):
  return int(bytes.encode('hex'), 16)

这种方法虽然简短易懂,但可能不太高效。是否有更好的(更明显的)方法?


你为什么认为它不是很高效?更重要的是,你为什么认为这会成为你编写的任何代码中的瓶颈? - abarnert
1
还有,'\x1134'是什么?你是指'\x11\x34'吗?还是'\\x1134'?因为你写的是一个由三个字节组成的字节串,其中包含了0x11、0x33和0x34字节,我认为这不是你所拥有或想要的。 - abarnert
1
你尝试过使用结构体吗?https://docs.python.org/2/library/struct.html - ovi
我已经将这些细节添加到问题中。它是一个可变大小的字节数组,我的示例是错误的。代码可能不会成为任何瓶颈(否则应该使用C来完成),但我想知道是否有一种“规范最佳方法” - 特别是因为最好只有一种明显的方法来做到这一点。 - loopbackbee
@goncalopp:好的,这很合理。我已经重写了我的答案,使其更多地关注Pythonic性能。如果我能想到一种干净的方法来在NumPy中编写它,我也会添加进去。 - abarnert
显示剩余3条评论
2个回答

104

Python传统上不太使用C语言中的“大端格式数”,因为它们对于C来说太大了。(如果你正在处理2字节、4字节或8字节的数字,则struct.unpack是答案。)

但足够多的人厌倦了没有一个明显的方法来做到这一点,因此Python 3.2添加了一个方法int.from_bytes,它正是你想要的:

int.from_bytes(b, byteorder='big', signed=False)

很不幸,如果你使用的是旧版本的Python,你没有这个功能。那么,除了显而易见的选择之外(升级到3.2或更好的3.4),你还有哪些选项?


首先,看看你的代码。我认为 binascii.hexlify.encode('hex') 更好,因为 "encode" 对于字节字符串(而不是 Unicode 字符串)的方法似乎有些奇怪,实际上在 Python 3 中已经被淘汰。但其他方面,它对我来说似乎相当易读和明显。并且它应该非常快 - 是的,它必须创建一个中间字符串,但它正在 C 中执行所有循环和算术运算(至少在 CPython 中),这通常比在 Python 中快一个或两个数量级。除非你的bytearray非常大,分配字符串本身将是昂贵的,否则我不会担心性能问题。

或者,你可以用循环来完成。但那会变得更冗长,并且至少在 CPython 中要慢得多。

你可以尝试消除显式循环以获得隐式循环,但显然要做到这一点的函数是reduce,这被部分社区认为不符合 Python 的风格,并且当然需要为每个字节调用函数。

你可以展开循环或reduce,通过将它们分解成 8 个字节的块并循环访问 struct.unpack_from,或者只需执行一个大的struct.unpack('Q'*len(b)//8 + 'B' * len(b)%8) 并循环访问它,但这会使它变得不那么易读,而且可能并没有快多少。

你可以使用 NumPy… 但如果你的数据比 64 或者也许是 128 位还要大,它最终会将所有东西都转换为 Python 对象。

因此,我认为你的答案是最好的选择。


下面是一些时间比较,将其与最明显的手动转换进行比较:

import binascii
import functools
import numpy as np

def hexint(b):
    return int(binascii.hexlify(b), 16)

def loop1(b):
    def f(x, y): return (x<<8)|y
    return functools.reduce(f, b, 0)

def loop2(b):
    x = 0
    for c in b:
        x <<= 8
        x |= c
    return x

def numpily(b):
    n = np.array(list(b))
    p = 1 << np.arange(len(b)-1, -1, -1, dtype=object)
    return np.sum(n * p)

In [226]: b = bytearray(range(256))

In [227]: %timeit hexint(b)
1000000 loops, best of 3: 1.8 µs per loop

In [228]: %timeit loop1(b)
10000 loops, best of 3: 57.7 µs per loop

In [229]: %timeit loop2(b)
10000 loops, best of 3: 46.4 µs per loop

In [283]: %timeit numpily(b)
10000 loops, best of 3: 88.5 µs per loop

在Python 3.4中进行比较:

In [17]: %timeit hexint(b)
1000000 loops, best of 3: 1.69 µs per loop

In [17]: %timeit int.from_bytes(b, byteorder='big', signed=False)
1000000 loops, best of 3: 1.42 µs per loop

所以,你的方法仍然非常快...


Python3的支持绝对是一个加分项。在这种情况下,它已经足够高效满足我的需求,我只是想确保没有更明显的方法。 - loopbackbee

2

1
只有当它是固定长度的字节数组(例如,2个字节长)时才有效。 - abarnert
确实,抱歉,我忘了提到这一点。与此同时,我已经编辑了问题。 - loopbackbee

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