Maneira mais leve de criar uma string aleatória e um número hexadecimal aleatório

96

Qual é a maneira mais leve de criar uma string aleatória de 30 caracteres como a seguinte?

ufhy3skj5nca0d2dfh9hwd2tbk9sw1

E um número hexadecimal de 30 dígitos como o seguinte?

8c6f78ac23b4a7b8c0182d7a89e9b1

xRobot
fonte
3
Como (por quê?) Nenhuma das respostas (bem elaboradas) foi aceita?
r2evans de

Respostas:

127

Eu tenho um mais rápido para a saída hexadecimal. Usando o mesmo t1 e t2 acima:

>>> t1 = timeit.Timer("''.join(random.choice('0123456789abcdef') for n in xrange(30))", "import random")
>>> t2 = timeit.Timer("binascii.b2a_hex(os.urandom(15))", "import os, binascii")
>>> t3 = timeit.Timer("'%030x' % random.randrange(16**30)", "import random")
>>> for t in t1, t2, t3:
...     t.timeit()
... 
28.165037870407104
9.0292739868164062
5.2836320400238037

t3 faz apenas uma chamada para o módulo aleatório, não tem que construir ou ler uma lista e então faz o resto com formatação de string.

Jcdyer
fonte
5
Agradável. Basta gerar um número aleatório de 30 dígitos hexadecimais e imprimi-lo. Óbvio quando apontado. Agradável.
eemz
Interessante, eu meio que esqueci que Python (e o módulo aleatório) lida com bigints nativamente.
wump
3
Veja a resposta de yaronf abaixo sobre como usar string.hexdigits: stackoverflow.com/a/15462293/311288 " string.hexdigitsretorna 0123456789abcdefABCDEF(tanto em minúsculas quanto em maiúsculas), [...]. Em vez disso, apenas use random.choice('0123456789abcdef')."
Thomas
3
Use em getrandbitsvez de randrangepara torná-lo ainda mais rápido.
robinst
@robinst tem um bom argumento. '%030x' % random.getrandbits(60)é ainda mais rápido do que '%030x' % random.randrange(16**30), provavelmente porque não precisa fazer nenhuma conversão de / para big-ints
Dan Lenski
81

String hexadecimal de 30 dígitos:

>>> import os,binascii
>>> print binascii.b2a_hex(os.urandom(15))
"c84766ca4a3ce52c3602bbf02ad1f7"

A vantagem é que ele obtém aleatoriedade diretamente do sistema operacional, que pode ser mais seguro e / ou mais rápido do que random (), e você não precisa gerá-lo.

wump
fonte
isso é interessante e provavelmente uma boa escolha para gerar o número hexadecimal de 30 dígitos que ele deseja. provavelmente poderia usar urandom e um operador de fatia para gerar a string alfanumérica também.
eemz
Eu dei uma olhada nas outras funções em binascii, elas têm base64 e uuencode, mas nenhuma maneira de gerar o primeiro tipo de string que ele quer (base36).
wump
1
Isso é aleatório / exclusivo o suficiente para ser usado em, digamos, tokens de sessão?
moraes
1
R: Não. A resposta foi encontrada
moraes
Como especificar o comprimento?
3kstc
55

No Py3.6 +, outra opção é usar o novo secretsmódulo padrão :

>>> import secrets
>>> secrets.token_hex(15)
'8d9bad5b43259c6ee27d9aadc7b832'
>>> secrets.token_urlsafe(22)   # may include '_-' unclear if that is acceptable
'teRq7IqhaRU0S3euX1ji9f58WzUkrg'
Um campeão
fonte
28
import string
import random
lst = [random.choice(string.ascii_letters + string.digits) for n in xrange(30)]
str = "".join(lst)
print str
ocwbKCiuAJLRJgM1bWNV1TPSH0F2Lb
Eemz
fonte
6
e random.choice (string.hexdigits)
eemz
1
Pode-se preferir o mais criptograficamente segurorandom.SystemRandom().choice
Brian M. Hunt
1
xrange () deve ser range () - NameError: nome 'xrange' não está definido
Caleb Bramwell
xrange está correto (e geralmente melhor) em python 2.x
jcdyer
25

Solução dramaticamente mais rápida do que as aqui:

timeit("'%0x' % getrandbits(30 * 4)", "from random import getrandbits")
0.8056681156158447
Kurt Spindler
fonte
Um usuário deve tentar todos os métodos na mesma máquina para obter uma linha de base precisa. As especificações de hardware podem fazer uma grande diferença. >>>> timeit.timeit ("'% 0x'% getrandbits (30 * 4)", "de importação aleatória getrandbits") 0,2471246949999113 </pre>
ptay
Obrigado, isso muito mais rápido do que os outros acima. %timeit '%030x' % randrange(16**30)dá 1.000.000 de loops, melhor de 3: 1,61 µs por loop, enquanto %timeit '%0x' % getrandbits(30 * 4)dá 1.000.000 de loops, melhor de 3: 396 ns por loop
frmdstryr
15

Nota: random.choice(string.hexdigits)está incorreto, porque string.hexdigitsretorna 0123456789abcdefABCDEF(tanto em minúsculas quanto em maiúsculas), então você obterá um resultado tendencioso, com o dígito hex 'c' duas vezes mais provável de aparecer do que o dígito '7'. Em vez disso, basta usar random.choice('0123456789abcdef').

Yaronf
fonte
6

Outro Método:

from Crypto import Random
import binascii

my_hex_value = binascii.hexlify(Random.get_random_bytes(30))

O ponto é: o valor do byte é sempre igual ao valor em hexadecimal .

dsgdfg
fonte
5

função de uma linha:

import random
import string

def generate_random_key(length):
    return ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length))

print generate_random_key(30)
Bouba
fonte
3
In [1]: import random                                    

In [2]: hex(random.getrandbits(16))                      
Out[2]: '0x3b19'
Prumo
fonte
Para sua informação: esta resposta foi sinalizada como de baixa qualidade, você pode querer melhorá-la.
oguz ismail
Alguma sugestão de melhoria?
Bob
Nenhuma idéia. Achei que você deveria saber disso
oguz ismail
2

A propósito, este é o resultado do uso timeitdas duas abordagens que foram sugeridas:

Usando random.choice():

>>> t1 = timeit.Timer("''.join(random.choice(string.hexdigits) for n in xrange(30))", "import random, string")
>>> t1.timeit()
69.558588027954102

Usando binascii.b2a_hex():

>>> t2 = timeit.Timer("binascii.b2a_hex(os.urandom(15))", "import os, binascii")
>>> t2.timeit()
16.288421154022217
David Narayan
fonte
2

Existe um mais rápido em comparação com o que jcdyer mencionou. Isso leva cerca de 50% de seu método mais rápido.

from numpy.random.mtrand import RandomState
import binascii
rand = RandomState()

lo = 1000000000000000
hi = 999999999999999999
binascii.b2a_hex(rand.randint(lo, hi, 2).tostring())[:30]

>>> timeit.Timer("binascii.b2a_hex(rand.randint(lo,hi,2).tostring())[:30]", \
...                 'from __main__ import lo,hi,rand,binascii').timeit()
1.648831844329834         <-- this is on python 2.6.6
2.253110885620117         <-- this on python 2.7.5

Se você quiser em base64:

binascii.b2a_base64(rand.randint(lo, hi, 3).tostring())[:30]

Você pode alterar o parâmetro de tamanho passado para randint (último argumento) para variar o comprimento de saída com base em seus requisitos. Então, para um de 60 caracteres:

binascii.b2a_hex(rand.randint(lo, hi, 4).tostring())[:60]
Ethan
fonte
Uma ligeira variação: binascii.b2a_hex(np.random.rand(np.ceil(N/16)).view(dtype=int))[:N]onde N=30.
dan-man
@ dan-man Obrigado por este método opcional. No entanto, acho que ele consome pelo menos 5 vezes mais tempo. Você percebe isso também?
Ethan
0

adicionando mais uma resposta à combinação que tem um desempenho mais rápido do que a solução @eemz e também é totalmente alfanumérica. Observe que isso não fornece uma resposta hexadecimal.

import random
import string

LETTERS_AND_DIGITS = string.ascii_letters + string.digits

def random_choice_algo(width):
  return ''.join(random.choice(LETTERS_AND_DIGITS) for i in range(width))

def random_choices_algo(width):
  return ''.join(random.choices(LETTERS_AND_DIGITS, k=width))


print(generate_random_string(10))
# prints "48uTwINW1D"

um rápido benchmark rende

from timeit import timeit
from functools import partial

arg_width = 10
print("random_choice_algo", timeit(partial(random_choice_algo, arg_width)))
# random_choice_algo 8.180561417000717
print("random_choices_algo", timeit(partial(random_choices_algo, arg_width)))
# random_choices_algo 3.172438014007639
andykais
fonte
0

Esta certamente não é a versão mais leve, mas é aleatória e é fácil de ajustar o alfabeto / comprimento que você deseja:

import random

def generate(random_chars=12, alphabet="0123456789abcdef"):
    r = random.SystemRandom()
    return ''.join([r.choice(alphabet) for i in range(random_chars)])
Martin Thoma
fonte