如何将字节字符串转换为整数?

171

如何在Python中将字节字符串转换为整数?

比如这样:'y\xcc\xa6\xbb'

我想出了一个聪明/愚蠢的方法:

sum(ord(c) << (i * 8) for i, c in enumerate('y\xcc\xa6\xbb'[::-1]))

我知道标准库中一定有内置的方法可以更简单地做到这一点......

这与将十六进制数字字符串转换为整数的操作不同,对于前者,您可以使用int(xxx,16),但我想要将实际字节值的字符串转换为整数。

更新:

我有点喜欢James的答案,因为它不需要导入其他模块,但Greg的方法更快:

>>> from timeit import Timer
>>> Timer('struct.unpack("<L", "y\xcc\xa6\xbb")[0]', 'import struct').timeit()
0.36242198944091797
>>> Timer("int('y\xcc\xa6\xbb'.encode('hex'), 16)").timeit()
1.1432669162750244

我用的笨拙方式:

>>> Timer("sum(ord(c) << (i * 8) for i, c in enumerate('y\xcc\xa6\xbb'[::-1]))").timeit()
2.8819329738616943

进一步更新:

有人在评论中问导入另一个模块的问题。好吧,导入模块并不一定是廉价的,请看:

>>> Timer("""import struct\nstruct.unpack(">L", "y\xcc\xa6\xbb")[0]""").timeit()
0.98822188377380371

将导入模块的成本计算在内几乎抵消了该方法的所有优势。我认为这只会包括整个基准运行中导入一次的费用;看看当我每次强制重新加载它时会发生什么:

>>> Timer("""reload(struct)\nstruct.unpack(">L", "y\xcc\xa6\xbb")[0]""", 'import struct').timeit()
68.474128007888794

毋庸置疑,如果您对这个方法进行了大量的执行,并且仅有一个导入,则此问题在成比例上变得不那么重要。这也可能是I/O成本而非CPU成本,因此可能取决于特定机器的容量和负载特性。


从标准库中导入东西为什么不好? - user3850
andyway,重复的内容:https://dev59.com/KnVD5IYBdhLWcg3wWaVh - user3850
28
你的“进一步更新”有些奇怪...为什么要这么频繁地导入模块? - user3850
5
我知道这是一个老问题。但如果你想让其他人了解最新情况,机械蜗牛的答案(int.from_bytes)在我的电脑上比struct.unpack表现更好。我认为它更易读。 - magu_
13个回答

358
在Python 3.2及以上版本中,请使用
>>> int.from_bytes(b'y\xcc\xa6\xbb', byteorder='big')
2043455163

或者

>>> int.from_bytes(b'y\xcc\xa6\xbb', byteorder='little')
3148270713

根据您的字节串字节序。对于任意长度的字节串整数以及通过指定signed=True来处理二进制补码有符号整数,这也适用。请参阅from_bytes文档

@eri 慢了多少?我以前用的是 struct,但在转到 py3 时改成了 int.from_bytes。由于我正在接收串行数据,所以每毫秒都要调用此方法,因此任何加速都是受欢迎的。我一直在研究这个。 - Naib
@Naib,对于os.urandom(4)字节,在我的CPU上,使用struct的速度为1.4微秒,而使用int.from_bytes的速度为2.3微秒。Python 3.5.2。 - eri
5
@eri,我恢复了一个用于评估几种CRC方法的timeit脚本。 四个运行:1)结构体,2)int.from_bytes,3)像#1一样但使用Cython编译,4)像#2一样但使用Cython编译。 结构体需要330ns,int需要1.14us(在Cython中都快了大约20ns...)看起来我要切换回原来的方法 :) 这不是过早的优化,我一直在解决一些严重的瓶颈问题,特别是需要后处理一百万个样本并且一直在尝试解决这些问题。 - Naib

112
你也可以使用struct模块来实现这个功能:
>>> struct.unpack("<L", "y\xcc\xa6\xbb")[0]
3148270713L

4
警告:在64位Python版本中,"L"实际上是8个字节(而不是4个字节),因此这可能会导致错误。 - Rafał Dowgird
12
Rafał说:“实际上不是这样的,因为根据文档,在格式字符串以'<'、'>'、'!'或'='之一开头时,L是标准尺寸(4)。” 参考网址:http://docs.python.org/library/struct.html#format-characters - André Laszlo
61
这个答案不适用于任意长度的二进制字符串。 - amcnabb
4
类型具有特定的大小,这对于任意长度的二进制字符串是行不通的。如果您知道每个项目的类型,可以设置一个for循环来处理它。 - Joshua Olson
3
"L" 实际上是一个 uint32(4个字节)。如果像我这样需要8个字节,则使用 "Q"-->uint64。还要注意,"l"-->int32 和 q-->int64。 - ntg
显示剩余3条评论

68

正如 Greg 所说,如果你在处理二进制值,你可以使用 struct,但是如果你只有一个以字节格式呈现的“十六进制数”,你可能只需要像这样进行转换:

s = 'y\xcc\xa6\xbb'
num = int(s.encode('hex'), 16)

...这与以下代码等效:

num = struct.unpack(">L", s)[0]

...除了它适用于任意数量的字节。


3
“二进制值”和“以字节格式呈现的‘十六进制数’”之间到底有什么区别? - user3850
请参考“帮助结构体”。例如,“001122334455”.decode('hex') 无法使用结构体转换为数字。 - James Antill
3
顺便提一下,本答案假定整数是以大端字节顺序编码的。如果是小端顺序,请使用以下代码: int(''.join(reversed(s)).encode('hex'), 16) - amcnabb
1
好的,但这会很慢!如果你在编写Python代码,那么这似乎并不重要。 - MattCochrane

8
我使用以下函数在int、hex和bytes之间进行数据转换。
def bytes2int(str):
 return int(str.encode('hex'), 16)

def bytes2hex(str):
 return '0x'+str.encode('hex')

def int2bytes(i):
 h = int2hex(i)
 return hex2bytes(h)

def int2hex(i):
 return hex(i)

def hex2int(h):
 if len(h) > 1 and h[0:2] == '0x':
  h = h[2:]

 if len(h) % 2:
  h = "0" + h

 return int(h, 16)

def hex2bytes(h):
 if len(h) > 1 and h[0:2] == '0x':
  h = h[2:]

 if len(h) % 2:
  h = "0" + h

 return h.decode('hex')

来源: http://opentechnotes.blogspot.com.au/2014/04/convert-values-to-from-integer-hex.html

本文介绍了如何在IT技术中将整数和16进制值相互转换。在计算机编程中,整数和16进制值是经常使用的数据类型。本文提供了一些简单易懂的方法,帮助读者快速地进行转换。

6
import array
integerValue = array.array("I", 'y\xcc\xa6\xbb')[0]

警告:以上内容非常依赖于特定的平台。 "I"指示符和字符串->整数转换的字节顺序都取决于您特定的Python实现。但是,如果您想一次转换多个整数/字符串,则array模块可以快速完成。


6
在Python 2.x中,您可以使用格式说明符<B表示无符号字节,<b表示有符号字节,用于struct.unpack/struct.pack

例如:

假设x = '\xff\x10\x11'

data_ints = struct.unpack('<' + 'B'*len(x), x) # [255, 16, 17]

并且:

data_bytes = struct.pack('<' + 'B'*len(data_ints), *data_ints) # '\xff\x10\x11'

该项为必填项!

请参见https://docs.python.org/2/library/struct.html#format-characters,了解格式说明符的列表。


3
>>> reduce(lambda s, x: s*256 + x, bytearray("y\xcc\xa6\xbb"))
2043455163

测试1:反转:

>>> hex(2043455163)
'0x79cca6bb'

测试2:字节数 > 8:

>>> reduce(lambda s, x: s*256 + x, bytearray("AAAAAAAAAAAAAAA"))
338822822454978555838225329091068225L

测试 3:递增一:

>>> reduce(lambda s, x: s*256 + x, bytearray("AAAAAAAAAAAAAAB"))
338822822454978555838225329091068226L

测试4:追加一个字节,比如 'A':

>>> reduce(lambda s, x: s*256 + x, bytearray("AAAAAAAAAAAAAABA"))
86738642548474510294585684247313465921L

测试 5:除以 256:

>>> reduce(lambda s, x: s*256 + x, bytearray("AAAAAAAAAAAAAABA"))/256
338822822454978555838225329091068226L

结果与第4个测试的结果相同,符合预期。


1
如上所述,使用 structunpack 函数是一个不错的方法。如果你想要实现自己的函数,还有另一种解决方案:
def bytes_to_int(bytes):
    result = 0
    for b in bytes:
        result = result * 256 + int(b)
return result

这对于被转换为字节的负数无效。 - Maria

1

如果您的版本>=3.2,int.from_bytes是最佳解决方案。"struct.unpack"解决方案需要一个字符串,所以对于字节数组不适用。这里有另一个解决方案:

def bytes2int( tb, order='big'):
    if order == 'big': seq=[0,1,2,3]
    elif order == 'little': seq=[3,2,1,0]
    i = 0
    for j in seq: i = (i<<8)+tb[j]
    return i

hex(bytes2int([0x87, 0x65, 0x43, 0x21]))返回'0x87654321'。

它处理大端和小端,并且可以轻松修改为8个字节。


1

我曾经苦于找不到适用于Python 2.x的任意长度字节序列解决方案。最终,我编写了这个函数。虽然它执行字符串转换有点hacky,但它确实有效。

Python 2.x 的任意长度函数

def signedbytes(data):
    """Convert a bytearray into an integer, considering the first bit as
    sign. The data must be big-endian."""
    negative = data[0] & 0x80 > 0

    if negative:
        inverted = bytearray(~d % 256 for d in data)
        return -signedbytes(inverted) - 1

    encoded = str(data).encode('hex')
    return int(encoded, 16)

这个函数有两个要求:
  • The input data needs to be a bytearray. You may call the function like this:

    s = 'y\xcc\xa6\xbb'
    n = signedbytes(s)
    
  • The data needs to be big-endian. In case you have a little-endian value, you should reverse it first:

    n = signedbytes(s[::-1])
    
当然,仅在需要任意长度时才使用此方法。否则,请使用更标准的方式(例如struct)。

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