我想要在 URL 中表示整数的最短方法。例如,可以使用十六进制将 11234 缩短为“2be2”。由于 base64 使用的是 64 字符编码,因此应该可以使用比十六进制更少的字符来表示 base64 中的整数。问题是我无法找出使用 Python 将整数转换为 base64(然后再返回)的最简洁方法。
base64 模块具有处理字节串的方法 - 所以也许一种解决方案是将整数转换为其二进制表示形式作为 Python 字符串......但我也不知道该怎么做。
这个答案在精神上与 Douglas Leeder 的相似,但有以下变化:
它不是先将数字转换为字节字符串(以 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))
'$_____'
一些旁注:
所有关于 Base64 的答案都是非常合理的解决方案。但它们在技术上是不正确的。要将整数转换为可能的最短 URL 安全字符串,您需要的是 base 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 可安装包使用:
您可能不想要真正的 base64 编码 - 它会添加填充等,甚至可能导致比十六进制更大的字符串。如果不需要与其他任何东西互操作,只需使用您自己的编码。例如。这是一个将编码为任何基数的函数(请注意,数字实际上首先存储为最低有效位,以避免额外的 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
这样做的好处是您可以使用您想要的任何基础,只需将适当的字符添加到编码器的基础字符串中即可。
请注意,较大基数的收益不会那么大。base 64 只会将大小减少到 base 16 的 2/3(6 位/字符而不是 4)。每次加倍只会为每个字符增加一位。除非你真的需要压缩东西,否则只使用十六进制可能是最简单和最快的选择。
编码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 个字符):
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:]
您不想要 base64 编码,您想以数字基数 X 表示以 10 为基数的数字。
如果您希望以 26 个字母表示以 10 为基数的数字,您可以使用: http ://en.wikipedia.org/wiki/Hexavigesimal 。(您可以通过使用所有合法的 url 字符来扩展该示例以获得更大的基础)
你至少应该能够得到基数 38(26 个字母,10 个数字,+,_)
简单的一点是将字节字符串转换为网络安全的 base64:
import base64
output = base64.urlsafe_b64encode(s)
棘手的一点是第一步 - 将整数转换为字节字符串。
如果你的整数很小,你最好对它们进行十六进制编码 - 见saua
否则(hacky递归版本):
def convertIntToByteString(i):
if i == 0:
return ""
else:
return convertIntToByteString(i >> 8) + chr(i & 255)
Base64 需要 4 个字节/字符来编码 3 个字节,并且只能编码 3 个字节的倍数(否则会添加填充)。
所以在 Base64 中表示 4 个字节(你的平均 int)需要 8 个字节。用十六进制编码相同的 4 个字节也需要 8 个字节。因此,您不会为单个 int 获得任何收益。
有点hacky,但它有效:
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 模块中的某些内容替换对 .encode('base64') 的调用,例如 urlsafe_b64encode()
我维护了一个名为 zbase62 的小库:http: //pypi.python.org/pypi/zbase62
有了它,您可以将 Python 2 str 对象转换为 base-62 编码字符串,反之亦然:
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 编码字符串会长 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'
我正在为此制作一个 pip 包。
我建议你使用我的 bases.py https://github.com/kamijotouma/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/kamijotouma/bases.py#known-basesalphabets 了解可用的碱基
对于您的情况
我建议您使用基数 32、58 或 64
Base-64 警告:除了有几个不同的标准外,当前没有添加填充,也没有跟踪行长。不建议与需要正式 base-64 字符串的 API 一起使用!
base 66 也是如此,目前 bases.js 和 bases.py 都不支持,但将来可能会
我会使用你建议的“将整数编码为二进制字符串,然后使用 base64 编码”方法,我会使用 struct:
>>> import struct, base64
>>> base64.b64encode(struct.pack('l', 47))
'LwAAAA=='
>>> struct.unpack('l', base64.b64decode(_))
(47,)
再次编辑:要去除因太小而需要完整 32 位精度的数字上的额外 0,请尝试以下操作:
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,没有依赖关系,没有字节字符串的编码等,只需使用正确的 RFC 4648 字符将 base 10 int 转换为 base 64 int:
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)
正如评论中提到的,您可以使用 URL 中未转义的 73 个字符对数据进行编码。我发现有两个地方使用了这种 Base73 URL 编码:
但实际上,您可以使用更多字符,如, , ,/
和[
其他]
一些字符。这些字符仅在您执行时转义,即您需要通过 get 参数传递数据。:
;
encodeURIComponent
所以实际上您最多可以使用 82 个字符。完整的字母表是!$&'()*+,-./0123456789:;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~
。我按它们的代码对所有符号进行了排序,因此当 Base82URL 数字被排序为纯字符串时,它们保持相同的顺序。
我在 Chrome 和 Firefox 中进行了测试,它们运行良好,但可能会让普通用户感到困惑。但是我将这些 id 用于没有人看到它们的内部 API 调用。
无符号整数 32 位的最大值可能为 2^32=4294967296 编码为 Base82 后将需要 6 个字符:$0~]mx
。
我在 Python 中没有代码,但这里有一个 JS 代码,它生成一个随机 id(int32 无符号)并将其编码为 Base82URL:
/**
* Convert uint32 number to Base82 url safe
* @param {int} number
* @returns {string}
*/
function toBase82Url(number) {
// all chars that are not escaped in url
let keys = "!$&'()*+,-./0123456789:;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~"
let radix = keys.length
let encoded = []
do {
let index = number% radix
encoded.unshift(keys.charAt(index))
number = Math.trunc(number / radix)
} while (number !== 0)
return encoded .join("")
}
function generateToken() {
let buf = new Uint32Array(1);
window.crypto.getRandomValues(buf)
var randomInt = buf[0]
return toBase82Url(randomInt)
}