【问题标题】:Efficient arbitrary-sized integer packing in PythonPython中高效的任意大小整数打包
【发布时间】:2009-04-22 14:36:12
【问题描述】:

struct.pack() 函数允许将最多 64 位的整数转换为字节字符串。打包更大整数的最有效方法是什么?我宁愿不添加对像 PyCrypto(提供 num_to_bytes())这样的非标准模块的依赖。

【问题讨论】:

  • 不确定你在说什么。是否要将 Python 的字符串版本 long 放入结构中? long 的字符串版本是字符串;你像任何其他字符串一样打包它。你真正的问题是什么?
  • OP 希望尽可能高效地将任意大小的整数打包成合理的字节字符串表示形式。

标签: python byte


【解决方案1】:

遇到了同样的问题。从python 3.2开始,可以使用int.to_bytes

>>> (2**100).to_bytes(16, byteorder='big')
b'\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

【讨论】:

  • 这绝对是2018年的正确答案;这是我正在寻找的功能。可悲的是,当我在 2009 年提出这个问题时,它并不存在。两年后,3.2 出现了。不过,我将此答案标记为已接受的答案,因为它在过去 6 年多的时间里都是正确的,而且这个问题没有被接受的答案。
【解决方案2】:

你的意思是像这样的某事吗:

def num_to_bytes(num):
    bytes = []
    num = abs(num) # Because I am unsure about negatives...
    while num > 0:
        bytes.append(chr(num % 256))
        num >>= 8
    return ''.join(reversed(bytes))

def bytes_to_num(bytes):
    num = 0
    for byte in bytes:
        num <<= 8
        num += ord(byte)
    return num

for n in (1, 16, 256, 257, 1234567890987654321):
    print n,
    print num_to_bytes(n).encode('hex'),
    print bytes_to_num(num_to_bytes(n))

返回:

1 01 1
16 10 16
256 0100 256
257 0101 257
1234567890987654321 112210f4b16c1cb1 1234567890987654321

我只是不知道如何处理底片......我对比特旋转不太熟悉。

编辑:另一种解决方案(根据我的测试,它的运行速度提高了大约 30%):

def num_to_bytes(num):
    num = hex(num)[2:].rstrip('L')
    if len(num) % 2:
        return ('0%s' % num).decode('hex')
    return num.decode('hex')

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

【讨论】:

  • 对,就是这个意思,前段时间写了这么一个函数。但是我想要一些更容易“移植”的东西,比如列表理解技巧或(更好的)我不知道的库函数。
  • 我刚刚发布了另一个带有内置 int/hex 转码和 hex/str 转码的 go... 它也快一点!
  • @noamtm:请用其他事实更新问题。不要只在 cmets 中隐藏信息。
【解决方案3】:

假设发布者想要将一个大整数打包为二进制字符串,即不使用数字中的每个数字存储一个字节。这样做的一种方法似乎是:

import marshal

a = 47L
print marshal.dumps(a)

打印出来:

'l\x01\x00\x00\x00/\x00'

我不能说我现在理解如何解释这些位......

【讨论】:

  • 方向是对的,但是前面还有两个多余的字节。
  • @Mike:实际上不止于此——我认为“l”和前 4 位数字只是内容的计数,后跟一个字节(“/” == chr(47))最后是一个空值。看起来 marshal 正在做一个更复杂的编码方案,只包括原始字节(例如查看 dumps(2**64-1) 而不是中间的 0x7f 字节。
【解决方案4】:

我认为你的意思是你只想使用尽可能多的字节来表示数字?例如如果号码是:

  • 255 或更少,您只使用 1 个字节
  • 65535 或更少 2 个字节
  • 16777215 或更少 3 个字节
  • 等等等等

在 Psion PDA 上,他们通常有一些打包方案,您可以在其中读取第一个字节,检测它是否设置了最高位,然后读取另一个字节(如果有)。这样,您只需继续读取字节,直到您读取“完整”数字。如果您处理的大多数数字都非常小,那么该系统运行良好,因为您通常每个数字只使用一到两个字节。

另一种方法是用一个(或多个)字节表示使用的总字节数,但此时它基本上是 Python 中的一个字符串。即它是一个 base-256 数字的字符串。

【讨论】:

    【解决方案5】:

    这有点老套,但你可以通过十六进制字符串表示,然后使用十六进制编解码器进行二进制:

    >>> a = 2**60
    >>> a
    1152921504606846976L
    >>> hex(a)
    '0x1000000000000000L'
    >>> hex(a).rstrip("L")[2:].decode('hex')
    '\x10\x00\x00\x00\x00\x00\x00\x00'       # 8bytes, as expected.
    
    >>> int(_.encode('hex'), 16)
    1152921504606846976L
    

    它有点中断,因为十六进制编解码器需要偶数位数,因此您需要对此进行填充,并且您需要设置一个标志来处理负数。这是一个通用的打包/解包:

    def pack(num):
        if num <0:
           num = (abs(num) << 1) | 1    # Hack - encode sign as lowest bit.
        else:
           num = num << 1
        hexval = hex(num).rstrip("L")[2:]
        if len(hexval)%2 ==1: hexval = '0' + hexval
        return hexval.decode('hex')
    
    def unpack(s):
        val = int(s.encode('hex'), 16)
        sign = -1 if (val & 1) else 1
        return sign * (val>>1)
    
    
    for i in [10,4534,23467, 93485093485, 2**50, 2**60-1, -1, -20, -2**60]:
        assert unpack(pack(i)) == i
    

    虽然需要对填充等进行所有摆弄,但我不确定它是否比手动解决方案好得多。

    【讨论】:

      【解决方案6】:

      正如 S.Lott 在评论中所建议的,只需将数字转换为字符串并打包该字符串。例如,

      x = 2 ** 12345
      struct.pack("40s", str(x))
      

      【讨论】:

      • 这甚至比只存储整数的字符串表示形式效率更低,因为用空字节填充 int。
      • 问题不清楚需要什么样的效率?我们是否正在寻找最有效的空间利用方式?或者也许是打包结构的最少处理时间?就此而言,效率是否重要?该问题要求最有效的答案,但人们往往过早地进行优化。
      【解决方案7】:

      这概括了先前的answer by Claudio。它尝试使用最佳字节长度。它还支持无符号和有符号整数。

      def int_to_bytes(i: int, *, signed: bool = False) -> bytes:
          length = (i.bit_length() + 7 + signed) // 8
          return i.to_bytes(length, byteorder='big', signed=signed)
      
      def bytes_to_int(b: bytes, *, signed: bool = False) -> int:
          return int.from_bytes(b, byteorder='big', signed=signed)
      
      # Test unsigned:
      for i in range(1025):
          assert i == bytes_to_int(int_to_bytes(i))
      
      # Test signed:
      for i in range(-1024, 1025):
          assert i == bytes_to_int(int_to_bytes(i, signed=True), signed=True)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-25
        • 1970-01-01
        • 2015-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多