Jak przekonwertować liczbę całkowitą do najkrótszego bezpiecznego ciągu URL w Pythonie?

Chcę jak najkrótszego sposobu reprezentowania liczby całkowitej w adresie URL. Na przykład, 11234 można skrócić do' 2be2 ' w systemie szesnastkowym. Ponieważ Base64 używa kodowania 64 znaków, powinno być możliwe reprezentowanie liczby całkowitej w base64 używając nawet mniej znaków niż szesnastkowy. Problem polega na tym, że nie mogę znaleźć najczystszego sposobu konwersji liczby całkowitej do base64 (i z powrotem) za pomocą Pythona.

Moduł base64 ma metody radzenia sobie z bajtami - więc może jedno rozwiązanie byłoby przekonwertowanie liczby całkowitej na jej binarną reprezentację jako ciąg Pythona... ale też nie wiem, jak to zrobić.

Author: user000001, 2009-02-18

14 answers

Ta odpowiedź jest podobna w duchu do Douglasa Leedera, z następującymi zmianami:]}

  • nie używa rzeczywistego Base64, więc nie ma znaków wypełniających
  • Zamiast konwertować liczbę jako pierwszą na łańcuch bajtowy (baza 256), konwertuje ją bezpośrednio do bazy 64, co ma tę zaletę, że pozwala reprezentować liczby ujemne za pomocą znaku znakowego.

    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))
    '$_____'

Kilka uwag pobocznych:

  • You could (marginalnie) zwiększ czytelność liczb bazowych 64 poprzez umieszczenie ciągu.cyfry pierwsze w alfabecie( i tworząc znak" -"); wybrałem kolejność, którą zrobiłem na podstawie kodu Urlsafe_b64encode Pythona.
  • jeśli kodujesz wiele liczb ujemnych, możesz zwiększyć wydajność, używając bitu znaku lub dopełniacza jedynki / dwójki zamiast znaku.
  • powinieneś być w stanie łatwo dostosować ten kod do różnych baz, zmieniając alfabet, aby ograniczyć go tylko do znaków alfanumerycznych lub dodać dodatkowe znaki "bezpieczne dla URL".
  • zalecałbym przeciwko używanie reprezentacji innej niż baza 10 W Uri w większości przypadków-zwiększa złożoność i utrudnia debugowanie bez znaczących oszczędności w porównaniu do kosztów HTTP-chyba że chcesz coś TinyURL-esque.
 60
Author: Miles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-25 00:08:29

Wszystkie odpowiedzi dotyczące Base64 są bardzo rozsądnymi rozwiązaniami. Ale są niepoprawne technicznie. Aby przekonwertować liczbę całkowitą na najkrótszy Bezpieczny ciąg URL, to, co chcesz, to baza 66 (istnieją 66 bezpieczne znaki url).

Ten kod wygląda tak:

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]

Oto pełna implementacja ze źródłowym i gotowym do instalacji pakietem pip:

Https://github.com/aljungberg/hexahexacontadecimal

 18
Author: Alexander Ljungberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-01 18:08:21

Prawdopodobnie nie chcesz do tego prawdziwego kodowania base64 - spowoduje to dodanie wypełnienia itp., potencjalnie nawet skutkując większymi ciągami niż hex dla małych liczb. Jeśli nie ma potrzeby współdziałania z czymkolwiek innym, po prostu użyj własnego kodowania. Np. oto funkcja, która będzie kodować do dowolnej bazy (zauważ, że cyfry są faktycznie przechowywane jako najmniej znaczące, aby uniknąć dodatkowych wywołań 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

To ma tę zaletę, że możesz użyć dowolnej bazy, po prostu dodając odpowiednie znaków do podstawowego ciągu kodera.

Zauważ, że zyski z większych BAZ nie będą jednak tak duże. base 64 zmniejszy rozmiar Tylko do 2/3rds z bazy 16 (6 bitów/znak zamiast 4). Każde podwojenie dodaje tylko jeden bit na znak. O ile nie masz prawdziwej potrzeby kompaktowania rzeczy, samo użycie hex będzie prawdopodobnie najprostszą i najszybszą opcją.

 14
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-21 22:38:14

Aby zakodować n:

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

Do dekodowania s:

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

W tym samym duchu co inne dla jakiegoś "optymalnego" kodowania, można użyć 73 znaki zgodnie z RFC 1738 (właściwie 74 jeśli policzysz " + " jako użyteczny):

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

I dekodowanie:

decoded = 0
while len(s) > 0:
    decoded = decoded * len(alphabet) + alphabet.find(s[0])
    s = s[1:]
 9
Author: kmkaplan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 21:49:50

Prosty bit konwertuje łańcuch bajtów do bezpiecznego dla sieci base64:

import base64
output = base64.urlsafe_b64encode(s)

Tricky bit jest pierwszym krokiem-konwersja liczby całkowitej na łańcuch bajtów.

Jeśli twoje liczby całkowite są małe, lepiej je zakodować hex - zobacz saua

Otherwise (hacky recursive version):

def convertIntToByteString(i):
    if i == 0:
        return ""
    else:
        return convertIntToByteString(i >> 8) + chr(i & 255)
 8
Author: Douglas Leeder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:20

Nie chcesz kodowania base64, chcesz reprezentować liczbę bazową 10 w bazie liczbowej X.

Jeśli chcesz, aby Twoja baza 10 była reprezentowana w 26 literach, możesz użyć: http://en.wikipedia.org/wiki/Hexavigesimal . (Możesz rozszerzyć ten przykład o znacznie większą bazę, używając wszystkich legalnych znaków url)

Powinieneś przynajmniej być w stanie uzyskać bazę 38 (26 liter, 10 cyfr,+,_)

 7
Author: Øystein E. Krog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 15:58:37

Base64 pobiera 4 bajty / znaki, aby zakodować 3 bajty i może kodować tylko wielokrotności 3 bajtów (w przeciwnym razie dodaje wypełnienie).

Zatem reprezentowanie 4 bajtów (Średnia wartość int) w Base64 zajęłoby 8 bajtów. Kodowanie tych samych 4 bajtów w szesnastkach zajęłoby również 8 bajtów. Więc nie zyskasz nic za jedną int.

 4
Author: Joachim Sauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 15:33:18

Trochę hacky, ale to działa:

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') 

Możesz zastąpić połączenie do .encode ('base64') z czymś w module base64, takim jak urlsafe_b64encode ()

 3
Author: ʞɔıu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 16:19:56

Prowadzę małą bibliotekę o nazwie zbase62: http://pypi.python.org/pypi/zbase62

Za jego pomocą można przekonwertować z obiektu Pythona 2 str na zakodowany łańcuch base-62 i odwrotnie:

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'

Jednak nadal musisz przekonwertować z integer na str. To jest wbudowane w Pythona 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'

Aby konwertować z int na bajty i odwrotnie w Pythonie 2, nie ma wygodnego, standardowego sposobu, o ile wiem. Chyba powinienem skopiować jakąś implementację, np. ten: https://github.com/warner/foolscap/blob/46e3a041167950fa93e48f65dcf106a576ed110e/foolscap/banana.py#L41{[4] do zbase62 dla Twojej wygody.

 3
Author: Zooko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-06 20:08:32

Jeśli szukasz sposobu na skrócenie reprezentacji liczb całkowitych przy użyciu base64, myślę, że powinieneś poszukać gdzie indziej. Kiedy kodujesz coś za pomocą base64, nie staje się to krótsze, w rzeczywistości staje się dłuższe.

Np. 11234 kodowany z base64 daje MTEyMzQ=

Używając base64 przeoczyłeś fakt, że nie konwertujesz tylko cyfr (0-9) na kodowanie 64 znaków. Konwertujesz 3 bajty na 4 bajty, więc masz gwarancję Base64 zakodowany ciąg znaków byłby o 33,33% dłuższy.

 2
Author: Sergey Golovchenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 15:35:18

Potrzebowałem podpisanej liczby całkowitej, więc skończyło się na:

import struct, base64

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

Przykład:

>>> b64encode_integer(1)
'AQAAAA'
>>> b64encode_integer(-1)
'_____w'
>>> b64encode_integer(256)
'AAEAAA'
 2
Author: toothygoose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-10 13:23:09

Pracuję nad przygotowaniem do tego pakietu pip.

Polecam skorzystanie z mojego bases.py https://github.com/kamijoutouma/bases.py który został zainspirowany bazami.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

Zobacz https://github.com/kamijoutouma/bases.py#known-basesalphabets dla jakich baz są użyteczne

W Twoim przypadku

Zalecam użycie bazy 32, 58 lub 64

Base-64 ostrzeżenie: poza tym istnieje kilka różnych standardów, padding nie jest obecnie dodawany i długość linii nie jest śledzona. Nie zaleca się stosowania z interfejsami API, które oczekują formalnej podstawy-64 struny!

To samo dotyczy bazy 66, która obecnie nie jest obsługiwana przez obie bazy.js i bases.py ale może w przyszłości

 2
Author: Belldandu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-27 10:37:39

Zrobiłbym' encode integer as binary string, then base64 encode that ' metoda, którą sugerujesz, i zrobiłbym to używając struct:

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

Edytuj ponownie: Aby usunąć dodatkowe 0s na liczbach, które są zbyt małe, aby wymagały pełnej 32-bitowej precyzji, spróbuj tego:

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,)
 1
Author: Jorenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-24 21:05:16

czysty python, bez zależności, bez kodowania łańcuchów bajtów itp. , po prostu zamieniając bazę 10 int w bazę 64 Int z poprawnymi znakami 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)
 1
Author: J.J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-27 09:16:02