如何将整数转换为Python中最短的url安全string?

我想要在URL中表示一个整数的最短的方式。 例如,11234可以用hex缩写为“2be2”。 由于base64使用的是64位字符编码,所以应该可以使用甚至比hex更less的字符来表示base64中的整数。 问题是我无法弄清楚使用Python将整数转换为base64(以及再次返回)的最简洁的方法。

base64模块有处理字节串的方法 – 所以也许一个解决scheme是将一个整数转换为它的二进制表示forms作为一个Pythonstring…但我不知道如何做到这一点。

这个答案在道格拉斯·李德尔(Douglas Leeder)的精神上是相似的,有如下变化:

  • 它不使用实际的Base64,所以没有填充字符
  • 不是将数字首先转换为字节串(基数为256),而是直接将其转换为基数64,这有利于让您使用符号字符表示负数。

    import string ALPHABET = string.ascii_uppercase + string.ascii_lowercase + \ string.digits + '-_' ALPHABET_REVERSE = dict((c, i) for (i, c) in enumerate(ALPHABET)) BASE = len(ALPHABET) SIGN_CHARACTER = '$' def num_encode(n): if n < 0: return SIGN_CHARACTER + num_encode(-n) s = [] while True: n, r = divmod(n, BASE) s.append(ALPHABET[r]) if n == 0: break return ''.join(reversed(s)) def num_decode(s): if s[0] == SIGN_CHARACTER: return -num_decode(s[1:]) n = 0 for c in s: n = n * BASE + ALPHABET_REVERSE[c] return n 

  >>> num_encode(0) 'A' >>> num_encode(64) 'BA' >>> num_encode(-(64**5-1)) '$_____' 

一些附注:

  • 通过在字母表中首先放置string.digits(并使符号字符为“ – ”),可以( 略微 )增加base-64数字的可读性。 我select了基于Python的urlsafe_b64encode的命令。
  • 如果你编码了很多负数,你可以通过使用符号位或二进制补码来代替符号字符来提高效率。
  • 您应该可以通过更改字母表来轻松地将此代码调整为不同的基础,可以将其限制为仅包含字母数字字符或添加其他“URL安全”字符。
  • 在大多数情况下,我build议不要在URI中使用除10以外的表示forms,否则与HTTP的开销相比,它会增加复杂性,使得debugging更加困难,除非您需要TinyURLtypes的东西。

所有关于Base64的答案都是非常合理的解决scheme。 但是他们在技术上是不正确的。 要将一个整数转换为可能的最短的URL安全string ,你想要的是66(有66个URL安全字符 )。

该代码如下所示:

 from io import StringIO import urllib BASE66_ALPHABET = u"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.~" BASE = len(BASE66_ALPHABET) def hexahexacontadecimal_encode_int(n): if n == 0: return BASE66_ALPHABET[0].encode('ascii') r = StringIO() while n: n, t = divmod(n, BASE) r.write(BASE66_ALPHABET[t]) return r.getvalue().encode('ascii')[::-1] 

这里有一个完整的实现与源代码,并准备去PIP可安装包:

https://github.com/aljungberg/hexahexacontadecimal

你可能不希望真正的base64编码 – 它会添加填充等,甚至可能导致比hex更大的string对于小数字。 如果不需要与其他任何东西进行互操作,只需使用自己的编码即可。 例如。 这里是一个函数,将编码到任何基地(注意数字实际上存储最不重要首先避免额外的reverse()调用:

 def make_encoder(baseString): size = len(baseString) d = dict((ch, i) for (i, ch) in enumerate(baseString)) # Map from char -> value if len(d) != size: raise Exception("Duplicate characters in encoding string") def encode(x): if x==0: return baseString[0] # Only needed if don't want '' for 0 l=[] while x>0: l.append(baseString[x % size]) x //= size return ''.join(l) def decode(s): return sum(d[ch] * size**i for (i,ch) in enumerate(s)) return encode, decode # Base 64 version: encode,decode = make_encoder("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") assert decode(encode(435346456456)) == 435346456456 

这样做的好处是,只要在编码器的基本string中添加适当的字符,就可以使用任何您想要的基本信息。

请注意,大型基地的收益不会那么大。 基地64只会减less到基地16(6位/字符而不是4)的2/3的大小。 每增加一倍,每个字符只增加一位。 除非你真的需要压缩东西,否则使用hex可能是最简单和最快的select。

编码n

 data = '' while n > 0: data = chr(n & 255) + data n = n >> 8 encoded = base64.urlsafe_b64encode(data).rstrip('=') 

解码s

 data = base64.urlsafe_b64decode(s + '===') decoded = 0 while len(data) > 0: decoded = (decoded << 8) | ord(data[0]) data = data[1:] 

按照与其他“最佳”编码相同的精神,您可以根据RFC 1738使用73个字符(实际上,如果将“+”计数为74,则可以使用74个字符):

 alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_`\"!$'()*,-." encoded = '' while n > 0: n, r = divmod(n, len(alphabet)) encoded = alphabet[r] + encoded 

并解码:

 decoded = 0 while len(s) > 0: decoded = decoded * len(alphabet) + alphabet.find(s[0]) s = s[1:] 

简单的一点是将字节string转换为网页安全的base64:

 import base64 output = base64.urlsafe_b64encode(s) 

棘手的一点是第一步 – 将整数转换为字节string。

如果你的整数很小,你最好用hex编码 – 参见saua

否则(hackyrecursion版本):

 def convertIntToByteString(i): if i == 0: return "" else: return convertIntToByteString(i >> 8) + chr(i & 255) 

你不想要base64编码,你想代表一个基数为10的数字在基数X.

如果你想让你的基数为10的数字代表26个字母,你可以使用: http : //en.wikipedia.org/wiki/Hexavigesimal 。 (你可以通过使用所有合法的url字符来扩展这个例子的一个更大的基地)

你应该至less可以得到基数38(26个字母,10个数字,+,_)

Base64需要4个字节/字符来编码3个字节,并且只能编码3个字节的倍数(否则添加填充)。

所以在Base64中代表4个字节(你的平均值)需要8个字节。 以hex编码相同的4个字节也需要8个字节。 所以你不会为单个int获取任何东西。

有点哈克,但它的作品:

 def b64num(num_to_encode): h = hex(num_to_encode)[2:] # hex(n) returns 0xhh, strip off the 0x h = len(h) & 1 and '0'+h or h # if odd number of digits, prepend '0' which hex codec requires return h.decode('hex').encode('base64') 

你可以使用base64模块中的某些东西(比如urlsafe_b64encode())replace对.encode('base64')的调用。

我维护一个名为zbase62的小型库: http ://pypi.python.org/pypi/zbase62

有了它,您可以从Python 2 str对象转换为base-62编码的string,反之亦然:

 Python 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53) [GCC 4.5.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> d = os.urandom(32) >>> d 'C$\x8f\xf9\x92NV\x97\x13H\xc7F\x0c\x0f\x8d9}\xf5.u\xeeOr\xc2V\x92f\x1b=:\xc3\xbc' >>> from zbase62 import zbase62 >>> encoded = zbase62.b2a(d) >>> encoded 'Fv8kTvGhIrJvqQ2oTojUGlaVIxFE1b6BCLpH8JfYNRs' >>> zbase62.a2b(encoded) 'C$\x8f\xf9\x92NV\x97\x13H\xc7F\x0c\x0f\x8d9}\xf5.u\xeeOr\xc2V\x92f\x1b=:\xc3\xbc' 

但是,您仍然需要从整数转换为str。 这是内置到Python 3:

 Python 3.2 (r32:88445, Mar 25 2011, 19:56:22) [GCC 4.5.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> d = os.urandom(32) >>> d b'\xe4\x0b\x94|\xb6o\x08\xe9oR\x1f\xaa\xa8\xe8qS3\x86\x82\t\x15\xf2"\x1dL%?\xda\xcc3\xe3\xba' >>> int.from_bytes(d, 'big') 103147789615402524662804907510279354159900773934860106838120923694590497907642 >>> x= _ >>> x.to_bytes(32, 'big') b'\xe4\x0b\x94|\xb6o\x08\xe9oR\x1f\xaa\xa8\xe8qS3\x86\x82\t\x15\xf2"\x1dL%?\xda\xcc3\xe3\xba' 

要在Python 2中将int转换为字节,反之亦然,就我所知,没有一种方便,标准的方法。 我想也许我应该复制一些实现,如这个: https : //github.com/warner/foolscap/blob/46e3a041167950fa93e48f65dcf106a576ed110e/foolscap/banana.py#L41到zbase62为了您的方便。

如果你正在寻找一种方法来缩短使用base64的整数表示,我认为你需要看看其他地方。 当你使用base64编码时,它不会变短,实际上变得更长。

例如用base64编码的11234会产生MTEyMzQ =

在使用base64的时候,你忽略了不把数字(0-9)转换成64字符编码的事实。 你正在将3个字节转换成4个字节,所以你保证你的base64编码的string将长33.33%。

我需要一个有符号的整数,所以我结束了:

 import struct, base64 def b64encode_integer(i): return base64.urlsafe_b64encode(struct.pack('i', i)).rstrip('=\n') 

例:

 >>> b64encode_integer(1) 'AQAAAA' >>> b64encode_integer(-1) '_____w' >>> b64encode_integer(256) 'AAEAAA' 

我正在为此制作一个点子包。

我build议你使用我的bases.py https://github.com/kamijoutouma/bases.py ,它的灵感来自bases.js

 from bases import Bases bases = Bases() bases.toBase16(200) // => 'c8' bases.toBase(200, 16) // => 'c8' bases.toBase62(99999) // => 'q0T' bases.toBase(200, 62) // => 'q0T' bases.toAlphabet(300, 'aAbBcC') // => 'Abba' bases.fromBase16('c8') // => 200 bases.fromBase('c8', 16) // => 200 bases.fromBase62('q0T') // => 99999 bases.fromBase('q0T', 62) // => 99999 bases.fromAlphabet('Abba', 'aAbBcC') // => 300 

参考https://github.com/kamijoutouma/bases.py#known-basesalphabets什么基地可用;

为你的情况

我build议你使用基本32,58或64

Base-64警告:除了有几个不同的标准之外,当前没有添加填充,并且不跟踪行长度。 不推荐使用期望正式的base-64string的API!

base 66也是如此,目前bases.js和bases.py都不支持它,但可能在将来

我会去'编码整数作为二进制string,然后base64编码'你build议的方法,我会做的结构:

 >>> import struct, base64 >>> base64.b64encode(struct.pack('l', 47)) 'LwAAAA==' >>> struct.unpack('l', base64.b64decode(_)) (47,) 

再次编辑:若要去掉超出所有32位精度的数字,请尝试以下操作:

 def pad(str, l=4): while len(str) < l: str = '\x00' + str return str >>> base64.b64encode(struct.pack('!l', 47).replace('\x00', '')) 'Lw==' >>> struct.unpack('!l', pad(base64.b64decode('Lw=='))) (47,) 

纯python,没有依赖关系,没有字节string的编码等,只是把一个基地10 int到基地64 int与正确的RFC 4648字符:

 def tetrasexagesimal(number): out="" while number>=0: if number == 0: out = 'A' + out break digit = number % 64 out = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[digit] + out number /= 64 # //= 64 for py3 (thank spanishgum!) if number == 0: break return out tetrasexagesimal(1)