在Python中将64位整数转换为8个单独的1字节整数

14

在Python中,我获得了一个64位整数。这个整数是通过将多个不同的8位整数混合在一起创建的,形成一个巨大的64位整数。我的任务是再次将它们分离。

例如:

Source number: 2592701575664680400
Binary (64 bits): 0010001111111011001000000101100010101010000101101011111000000000
int 1: 00100011 (35)
int 2: 11111011 (251)
int 3: 00100000 (32)
int 4: 01011000 (88)
int 5: 10101010 (170)
int 6: 00010110 (22)
int 7: 10111110 (190)
int 8: 00000000 (0)

我想要做的是使用源数字2592701575664680373并返回长度为8的数组,其中数组中的每个整数都是上面列出的整数。

我原本打算使用struct,但老实说,阅读文档并没有很清楚地说明我如何完成这个任务。


你尝试过使用 divmod() 吗? - lenz
真糟糕,@PadraicCunningham,你是正确的。我使用了一个快速而不太好的工具,它不支持足够大的数字,并且用0截断了最后一部分。现在我运行了bin = '{0:064b}'.format(source),我看到你是正确的。 - JHixson
事实上,n 是奇数且末尾没有1确实让我感到困惑。 - Padraic Cunningham
我改变了源代码中的数字(似乎比更改二进制代码更好,鉴于答案)。 - JHixson
“struct” 的文档并不是很糟糕,你只需要将数字加载到字符串中,然后再将其拆分成字节即可。 - Brent Washburne
5个回答

10

解决方案

不将数字转换为字符串的解决方案:

x = 0b0010001111111011001000000101100010101010000101101011111000000000

numbers = list((x >> i) & 0xFF for i in range(0,64,8))
print(numbers)                    # [0, 190, 22, 170, 88, 32, 251, 35]
print(list(reversed(numbers)))    # [35, 251, 32, 88, 170, 22, 190, 0]

解释

在这里我使用了列表推导式,在i的增量上循环8次。因此,i取值为0, 8, 16, 24, 32, 40, 48, 56。 每次,位移运算符>>临时将数字x向下移动i位。这相当于除以256^i

因此,得到的数字是:

i = 0:   0010001111111011001000000101100010101010000101101011111000000000
i = 8:           00100011111110110010000001011000101010100001011010111110
i = 16:                  001000111111101100100000010110001010101000010110
i = 24:                          0010001111111011001000000101100010101010
i = 32:                                  00100011111110110010000001011000
i = 40:                                          001000111111101100100000
i = 48:                                                  0010001111111011
i = 56:                                                          00100011

使用& 0xFF,我选择该数字的最后8位。例如:

x >> 48:           001000111111101100100000
0xff:                              11111111
(x >> 48) & 0xff:  000000000000000000100000

由于前导零不重要,您得到了所需的数字。

结果被转换为列表,并按正常和反向顺序打印(就像OP想要的那样)。

性能

我将此结果的计时与本线程中提出的其他解决方案进行了比较:

In: timeit list(reversed([(x >> i) & 0xFF for i in range(0,64,8)]))
100000 loops, best of 3: 13.9 µs per loop

In: timeit [(x >> (i * 8)) & 0xFF for i in range(7, -1, -1)]
100000 loops, best of 3: 11.1 µs per loop

In: timeit [(x >> i) & 0xFF for i in range(63,-1,-8)]
100000 loops, best of 3: 10.2 µs per loop

In: timeit reversed(struct.unpack('8B', struct.pack('Q', x)))
100000 loops, best of 3: 3.22 µs per loop

In: timeit reversed(struct.pack('Q', x))
100000 loops, best of 3: 2.07 µs per loop

结果:我的解决方案不是最快的!目前来看,直接使用struct(如Mark Ransom所建议的)似乎是最快的代码片段。


1
你也可以使用[(n >> (i * 8)) & 0xFF for i in range(7, -1, -1)],无需再进行反转。 - Padraic Cunningham
由于某些原因,我得到了不同的时间结果。我正在使用Python 3.4.2的iPython 2.0.0版本,32位,在64位Windows机器上。 - jojonas
有一个简单的答案,你在第一段代码中什么也没做,你在列表中使用了生成器表达式。 - Padraic Cunningham
你仍然需要反转列表,“[(x >> (i * 8))&0xFF for i in range(7, -1, -1)]”可以给你正确顺序的输出。 - Padraic Cunningham
1
使用 struct 大约快三倍。 - Brent Washburne

9
在 Python 2.x 中,struct.pack 返回一个字节串。很容易将它转换为整数数组。
>>> bytestr = struct.pack('>Q', 2592701575664680400)
>>> bytestr
'#\xfb X\xaa\x16\xbd\xd0'
>>> [ord(b) for b in bytestr]
[35, 251, 32, 88, 170, 22, 189, 208]

在Python中,struct模块用于将Python对象转换为字节字符串,通常根据C结构打包规则进行打包。 struct.pack需要一个格式说明符(描述结构的字节应如何布置的字符串)和一些Python数据,并将其打包成字节字符串。相反地,struct.unpack获取格式说明符和字节字符串,并返回一组已解包的数据,再次以Python对象的形式呈现。
使用的格式说明符有两个部分。前导字符指定字符串的字节顺序(大小端)。以下字符指定正在打包或解包的结构字段的类型。因此,'>Q'表示将给定数据作为大端unsigned long long打包。要以相反的顺序获取字节,可以使用<代替小端。
最后的操作是列表推导式,它遍历字节字符串的字符并使用内置函数ord获取该字符的整数表示形式。
最后提醒:Python实际上没有整数大小的概念。在2.x中,有限制为32位的int和无限制大小的long。在3.x中,这两个被统一为单一类型。因此,即使此操作保证只给出占用一个字节的整数,如果在其他操作中使用它们,Python不会强制结果整数保持这种方式。

非常感谢您的解释!这不仅解决了我的问题,而且我现在对使用“struct”模块的能力更加自信。 - JHixson
@JHixson,你可以感谢zstewart,在我回答代码后,他添加了整个解释。 - Mark Ransom

2
bn = "0010001111111011001000000101100010101010000101101011111000000000"

print([int(bn[i:i+8], 2) for i in range(0,len(bn), 8)])
[35, 251, 32, 88, 170, 22, 190, 0]

如果您使用n的二进制表示,则输出将不同:

n = 2592701575664680373
bn = bin(n)

print([int(bn[i:i+8], 2) for i in range(0,len(bn), 8)])
[35, 251, 32, 88, 170, 22, 189, 181]

一些时间记录:

In [16]: %%timeit                                                
numbers = list((n >> i) & 0xFF for i in range(0,64,8))
list(reversed(numbers))
   ....: 
100000 loops, best of 3: 2.97 µs per loop

In [17]: timeit [(n >> (i * 8)) & 0xFF for i in range(7, -1, -1)]
1000000 loops, best of 3: 1.73 µs per loop

In [18]: %%timeit                                                
bn = bin(n)
[int(bn[i:i+8], 2) for i in range(0,len(bn), 8)]
   ....: 
100000 loops, best of 3: 3.96 µs per loop

您也可以使用divmod函数:

out = []
for _ in range(8):
    n, i = divmod(n, 256)
    out.append(i) 
out = out[::-1]

这几乎同样有效:

In [31]: %%timeit
   ....: n = 2592701575664680411
   ....: out = []
   ....: for _ in range(8):
   ....:     n, i = divmod(n, 1 << 8)
   ....:     out.append(i)
   ....: out[::-1]
   ....: 
100000 loops, best of 3: 2.35 µs per loop

在Python中进行位移操作并没有太多优势,我更倾向于使用你和其他人认为更易读的方法。


2
这里有一个使用struct的版本:
import struct
n = 2592701575664680400
bytes = struct.unpack('8B', struct.pack('Q', n))

这个bytes返回的顺序与您在问题中展示的顺序相反。

以下是性能统计数据:

python -m timeit -s "import struct" "struct.unpack('8B', struct.pack('Q', 2592701575664680400))"
1000000 loops, best of 3: 0.33 usec per loop

在我的电脑上,这种方法比位移方法快三倍。

1
你可以通过为64位整数指定字节顺序(例如大端字节序,使用>)来控制返回的字节顺序。 - Blckknght

0

这对于一堆unit64来说似乎更快。使用numpy。

from cytpes import *
import numpy as np
l1 = c_uint64 * 512
payload64 = l1(0)
payload8 = np.frombuffer(payload64, dtype=np.uint8)

其中payload8是一个np.unit8数组,大小比payload64大8倍,其中包含了转换后的字节。

对我而言,这比结构体变量更快...

for i in range(len(payload64)):
       payload8[i*8:i*8+8] = struct.unpack('8B', struct.pack('Q', payload64[i]))

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