68 votes

Comment convertir un entier en la plus courte chaîne sécurisée contre les URL en Python?

Je veux que le plus court chemin possible de représenter un nombre entier dans une URL. Par exemple, 11234 peut être raccourci pour "2be2' l'aide de l'hexadécimal. Depuis base64 utilise est un 64 codage des caractères, il devrait être possible de représenter un entier en base64 même en utilisant moins de caractères que hexadécimal. Le problème est que je n'arrive pas à comprendre la façon la plus propre de convertir un nombre entier en base64 (et en arrière de nouveau) à l'aide de Python.

Le base64 module a des méthodes pour traiter avec bytestrings - alors peut-être une solution serait de convertir un nombre entier en sa représentation binaire comme une chaîne Python... mais je ne suis pas sûr de la façon de faire qui soit.

64voto

Miles Points 12977

Cette réponse est similaire dans l'esprit de Douglas Leeder, avec les modifications suivantes:

  • Il n'utilise pas de réelle Base64, donc il n'y a pas les caractères de remplissage
  • Au lieu de convertir le nombre d'abord à un octet string (base 256), il convertit directement à la base de 64 ans, qui a l'avantage de vous laisser représenter les nombres négatifs à l'aide d'un signe.

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

A côté de quelques remarques:

  • Vous pouvez (légèrement) augmenter la lisibilité de la base-64 numéros en plaçant la chaîne.chiffres en premier dans l'alphabet (et en faisant le signe '-'); j'ai choisi l'ordre que j'ai fait basé sur Python urlsafe_b64encode.
  • Si vous êtes encodage beaucoup de négatif numéros, vous pouvez augmenter l'efficacité en utilisant un bit de signe ou de/en complément à deux au lieu d'un signe de caractère.
  • Vous devriez être en mesure de facilement adapter ce code pour différentes bases par l'évolution de l'alphabet, soit la restreindre à seulement des caractères alphanumériques ou pour ajouter d'autres "URL-safe" des personnages.
  • Je recommanderais contre à l'aide d'une représentation autre que la base 10 dans les Uri dans la plupart des cas-cela ajoute à la complexité et rend le débogage plus difficile sans d'importantes économies par rapport à la surcharge de HTTP, sauf si vous allez pour quelque chose TinyURL-esque.

18voto

Toutes les réponses concernant Base64 sont très raisonnables solutions. Mais ils sont techniquement incorrect. Pour convertir un nombre entier pour la plus courte URL sécurité de la chaîne possible, ce que vous voulez, c'est la base de 66 (il y a 66 URL sûr caractères).

Ce code ressemble à ceci:

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]

Voici une mise en œuvre complète à la source et prêt à aller pip paquet installable:

https://github.com/aljungberg/hexahexacontadecimal

15voto

Brian Points 48423

Vous ne voulez probablement pas réel de l'encodage base64 pour cela -, il va ajouter du rembourrage, etc, peut-être même plus volumineuses que les cordes d'hex serait pour de petits nombres. Si il n'y a pas besoin d'interagir avec quoi que ce soit d'autre, il suffit d'utiliser votre propre codage. Par exemple. voici une fonction qui va encoder à toute la base (notez les chiffres sont en fait stockées moins significatif en premier pour éviter d'extra reverse() appelle:

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

Ceci a l'avantage que vous pouvez utiliser ce que vous voulez, simplement en ajoutant approprié les caractères de l'encodeur de la chaîne de base.

Notez que les gains pour les grandes bases ne sont pas va être grand cependant. la base de 64 ne fera que réduire la taille de la 2/3rds de la base 16 (6 bits/char au lieu de 4). Chaque doublement ajoute seulement un peu plus par personnage. Sauf si vous avez un réel besoin de compacter les choses, simplement en utilisant hex sera probablement la plus simple et la plus rapide de l'option.

9voto

kmkaplan Points 10338

Pour encoder n :

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

Pour décoder s :

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

Dans le même esprit que d'autres pour un codage "optimal", vous pouvez utiliser 73 caractères conformément à la RFC 1738 (en fait, 74 si vous comptez "+" comme utilisable):

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

et le décodage:

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

8voto

Douglas Leeder Points 29986

Le bit facile consiste à convertir la chaîne d'octets en base64 compatible Web:

 import base64
output = base64.urlsafe_b64encode(s)
 

Le bit le plus difficile est la première étape - convertissez l'entier en chaîne d'octets.

Si vos entiers sont petits, il vaut mieux les encoder en hexadécimal - voir saua

Sinon (version hacky récursive):

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X