使用大写字母和数字生成随机字符串

1729

如何生成一个大小为N的字符串,由数字和大写英文字母组成,例如:

  • 6U1S75
  • 4Z4UKK
  • U911K4

22
这是一个非常流行的问题。我希望有专家能够就前三个答案中随机数的独特性发表自己的看法,例如字符串大小范围内的碰撞概率,比如从6到16。 - user
10
计算可能组合的数量很容易。10个数字加上26个字母等于36种可能的字符,6位长度的字符串的组合数约为20亿。我的经验法则是“如果我为地球上的每个人都生成值,他们每个人可以拥有多少个值?”。在这种情况下,每个人拥有的值少于一个,因此如果要用于识别用户或对象,字符太少了。一个替代方案是添加小写字母,这将使您得到62的6次方,即近570亿个独特值。 - Blixt
4
虽然考虑全球人口可能看起来有些愚蠢,但这只是因为你想要一个巨大的缓冲区以防止潜在的碰撞。可以参考生日问题:http://en.wikipedia.org/wiki/Birthday_problem。 - Blixt
1
@buffer,你可能会对这个答案感兴趣。 - Anish Ramaswamy
1
你可以启动一个vim子进程,等待用户退出它,读取用户在尝试关闭vim时留下的文件,并从中过滤数字和大写拉丁字符。 - 12431234123412341234123
显示剩余2条评论
36个回答

3193

Answer in one line:

''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))

甚至可以使用Python 3.6开始更短的方式,使用random.choices()函数:

''.join(random.choices(string.ascii_uppercase + string.digits, k=N))

更具密码学安全性的版本: 请参阅此帖子

''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))

详细说明,包括一个可供进一步重用的清理函数:

>>> import string
>>> import random
>>> def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
...    return ''.join(random.choice(chars) for _ in range(size))
...
>>> id_generator()
'G5G74W'
>>> id_generator(3, "6793YUIO")
'Y3U'

它是如何工作的?

我们导入 string 模块,该模块包含常见 ASCII 字符序列,并导入 random 模块,该模块处理随机生成。

string.ascii_uppercase + string.digits 仅仅是将表示大写 ASCII 字符和数字的字符列表连接起来:

>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.digits
'0123456789'
>>> string.ascii_uppercase + string.digits
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

然后,我们使用列表推导式创建一个包含'n'个元素的列表:

>>> range(4) # range create a list of 'n' numbers
[0, 1, 2, 3]
>>> ['elem' for _ in range(4)] # we use range to create 4 times 'elem'
['elem', 'elem', 'elem', 'elem']

在上面的示例中,我们使用[创建列表,但在id_generator函数中没有使用,因此Python不会在内存中创建列表,而是逐个生成元素(有关详细信息,请参见此处)。
与其请求'elem'字符串创建'n'次,我们将请求Python从一系列字符中随机选择一个字符来创建'n'次:
>>> random.choice("abcde")
'a'
>>> random.choice("abcde")
'd'
>>> random.choice("abcde")
'b'

因此,random.choice(chars) for _ in range(size) 实际上创建了一个包含 size 个字符的序列,这些字符是从 chars 中随机选择的。
>>> [random.choice('abcde') for _ in range(3)]
['a', 'b', 'b']
>>> [random.choice('abcde') for _ in range(3)]
['e', 'b', 'e']
>>> [random.choice('abcde') for _ in range(3)]
['d', 'a', 'c']

然后我们只需用空字符串将它们连接起来,这样序列就变成了一个字符串:
>>> ''.join(['a', 'b', 'b'])
'abb'
>>> [random.choice('abcde') for _ in range(3)]
['d', 'c', 'b']
>>> ''.join(random.choice('abcde') for _ in range(3))
'dac'

8
不是列表推导式,而是生成器表达式。 - Ignacio Vazquez-Abrams
2
@joreilli:我在答案中添加了一个关于这个的快速说明,并提供了一个更详细的关于可迭代对象、列表推导式、生成器和最终的yield关键字的答案链接。 - Bite code
1
这种实现方式适合生成唯一序列吗?在6个字符的情况下,发生碰撞的概率是多少? - user
4
非常有用。有趣的是,Django正在使用这段代码生成密码和CSRF令牌。尽管你应该将 random 替换为 random.SystemRandom():https://github.com/django/django/blob/875ce287e25d7576f9bd102f86adae09d242360f/django/utils/crypto.py#L77 - user
2
@Chiel92,random.sample创建的样本是不重复的,换句话说,没有重复字符的可能性,这并不符合OP的要求。我认为对于大多数应用程序来说这并不理想。 - ontologist
显示剩余24条评论

647

这个Stack Overflow问题是“random string Python”的当前谷歌搜索结果中排名最高的。目前排名最高的答案是:

''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))

这是一个绝佳的方法,但是random库中的伪随机数生成器PRNG不具备加密安全性。我想很多研究这个问题的人都希望生成用于加密或密码的随机字符串。你可以通过对上述代码进行微小的更改来实现安全生成:

''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))

在需要安全的伪随机数生成器(PRNG)的应用程序中,使用random.choice而不是random.SystemRandom().choice可能会造成潜在的灾难性后果。使用random.SystemRandom()代替普通的random在*nix机器上使用/dev/urandom,在Windows上使用CryptGenRandom(),都是具有密码学安全性的PRNGs。

如果您正在使用Python3.6或更高版本,则可以使用新的secrets模块,正如MSeifert的答案中提到的那样:

''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(N))

模块文档还讨论了方便的方法来生成安全令牌最佳实践


7
是的,random官方标准库已经发出警告: "警告:本模块的伪随机生成器不应用于安全目的。如果您需要加密安全的伪随机数生成器,请使用os.urandom()或SystemRandom。" 参考资料:random.SystemRandomos.urandom - lord63. j
7
回答不错。小提醒:您将代码更改为 string.uppercase,这可能会根据设置的语言环境导致意外结果。在涉及编码的情况下,使用string.ascii_uppercase(或用于基于62进制的string.ascii_letters + string.digits 替代基于36进制的)更安全。 - Blixt
小提示 - 最好使用 xrange 而不是 range,因为后者会生成一个内存列表,而前者则创建一个迭代器。 - Guy
xrange并不是一个小注释(除非这里的每个人都在使用Python 3=P)。我真的很困惑,为什么大多数这些示例都完全忽略了性能。另一件事就是不要在循环中连接字符串,这会产生巨大的差异。这段代码使用xrange和只执行一次alphabet = string.ascii_uppercase + string.digits可以使速度提高约20%(在笔记本电脑上使用time进行了非常不科学的测试)。 - Marek Sapota
2
随机字符串是否总是唯一的?我想使用主键。 - shakthydoss
5
不行,它可能会返回“AAA000”,这是一个随机字符串,接下来可能是另一个“AAA000”,也是一个随机字符串。您必须明确添加检查唯一性的步骤。 - Jongware

267

使用Python内置的uuid:

如果UUID符合您的需求,请使用内置的uuid包。

一行代码解决方案:

import uuid; uuid.uuid4().hex.upper()[0:6]

详细版本:

示例:

import uuid
uuid.uuid4() #uuid4 => full random uuid
# Outputs something like: UUID('0172fc9a-1dac-4414-b88d-6b9a6feb91ea')

如果您需要确切的格式(例如,“6U1S75”),您可以像这样操作:

import uuid

def my_random_string(string_length=10):
    """Returns a random string of length string_length."""
    random = str(uuid.uuid4()) # Convert UUID format to a Python string.
    random = random.upper() # Make all characters uppercase.
    random = random.replace("-","") # Remove the UUID '-'.
    return random[0:string_length] # Return the random string.

print(my_random_string(6)) # For example, D9E50C

19
+1 对于问题背后的思考。也许您可以简要解释一下uuid1和uuid4之间的区别。 - Thomas Ahle
12
uui1:从主机ID、序列号和当前时间生成UUID。 uuid4:生成随机UUID。 - Bijan
9
如果您想跳过字符串转换和连字符替换,可以直接调用my_uuid.get_hex()或uuid.uuid4().get_hex(),它将返回从UUID生成的不带连字符的字符串。 - dshap
10
缩短 UUID 是个好主意吗?根据 string_length 的大小不同,碰撞的可能性会成为一个问题。 - user
3
为什么只限制于使用十六进制字符呢?可以使用Base64或Base32(仅包含大写字母和6个不同数字)对随机生成的 os.urandom() 字节序列进行编码。这样可以跳过中间的 uuid 步骤,提高编码速度! - Martijn Pieters
显示剩余6条评论

59

一种更简单、更快速但略微不太随机的方法是使用random.sample而不是逐个选择每个字母。如果允许n次重复,则可以将您的随机基础扩大n倍,例如:

import random
import string

char_set = string.ascii_uppercase + string.digits
print ''.join(random.sample(char_set*6, 6))
注意:random.sample 避免字符重复,增大字符集的大小可以使多次重复变得可能,但是它们仍然比在纯随机选择中更不可能发生。如果我们选取长度为6的字符串,并将第一个字符选为 'X',在纯随机选择示例中,获得第二个字符为 'X' 的概率与获得第一个字符为 'X' 的概率相同。在 random.sample 实现中,获得任何后续字符为 'X' 的概率只有作为第一个字符时的6/7.

10
这种方法并不差,但和逐个选择每个字符相比不够随机。使用 sample 方法你永远不会得到重复的字符。当然,对于大于 36N 值,这种方法会失败。 - bobince
5
一个例子出现了重复,因此我怀疑他并不想禁止重复。 - Mark Byers
5
如果使用random.sample函数避免重复字符,增加字符集大小可以使多次重复成为“可能”,但它们仍然比在纯随机选择中出现的概率要低。如果我们选取长度为6的字符串,并将第一个字符选为'X',在随机选择的例子中,获取第二个字符为'X'的概率与获取第一个字符为'X'的概率相同。但如果使用random.sample实现,获取任何后续字符为'X'的概率只有获取第一个字符为'X'的概率的5/6。 - pcurry
1
随着所生成字符串的移动,得到一个特定字符重复的机会逐渐降低。从26个大写字母和10个数字中随机选择各个字符,产生一个由6个字符组成的字符串,任何特定的字符串出现的频率均为1/(36^6)。生成'FU3WYE'和'XXXXXX'的机会相同。在样例实现中,生成'XXXXXX'的机会为(1/(36^6))((6/6)(5/6)(4/6)(3/6)(2/6)(1/6)),这是由于random.sample的非替换特性导致的。在样例实现中,'XXXXXX'的概率要少324倍。 - pcurry
@pcurry 是的,我同意在选择这个解决方案时应该考虑到这一点,我会把它加入到答案中,谢谢。 - Anurag Uniyal
显示剩余4条评论

38
import uuid
lowercase_str = uuid.uuid4().hex  

lowercase_str 是一个类似于 'cea8b32e00934aaea8c005a35d85a5c0' 的随机值。

uppercase_str = lowercase_str.upper()

uppercase_str'CEA8B32E00934AAEA8C005A35D85A5C0'


2
uppercase_str[:N+1] - Yajo
@Yajo 是的,我们可以使用切片来限制。 - Savad KP
2
@Yajo:不,您不希望切割十六进制值。这会减少与全大写字母和数字序列相比的熵。也许将该值进行base32编码(熵略有减少,从36 ** n 到 32 ** n,仍然优于16 ** n)。 - Martijn Pieters
1
@Yajo 一些 uuid 中的位数不是随机的!它们用于指示 uuid 的变体和版本,因此它们不是随机的,并且你得到的随机位数比你预期的要少! 要么完全理解截断时 UUID 的工作方式(阅读RFC),或者更好的方法是使用 python 秘密模块(或python2相当的random.SystemRandom()),因为这可以提供安全保证(与当前的uuid模块相比)。 - xuiqzy

26
从Python 3.6开始,如果您需要加密安全性,请使用 secrets模块 而不是random模块(否则此答案与@Ignacio Vazquez-Abrams的答案相同):
from secrets import choice
import string

''.join([choice(string.ascii_uppercase + string.digits) for _ in range(N)])

另外需要注意的是,使用列表推导式在使用str.join时比使用生成器表达式更快!


20

更快、更简便、更灵活的方法是使用strgen模块(pip install StringGenerator)。

生成一个包含大写字母和数字的6位随机字符串:

>>> from strgen import StringGenerator as SG
>>> SG("[\u\d]{6}").render()
u'YZI2CI'

获取唯一列表:

>>> SG("[\l\d]{10}").render_list(5,unique=True)
[u'xqqtmi1pOk', u'zmkWdUr63O', u'PGaGcPHrX2', u'6RZiUbkk2i', u'j9eIeeWgEF']

保证字符串中有一个“特殊”字符:

>>> SG("[\l\d]{10}&[\p]").render()
u'jaYI0bcPG*0'

一个随机的HTML颜色:

>>> SG("#[\h]{6}").render()
u'#CEdFCa'

等等。

我们需要意识到这一点:

''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))

可能不包含数字(或大写字母)。

strgen 在开发时间上比以上任何解决方案更快。Ignacio 的解决方案是运行时性能最快的,并且使用 Python 标准库是正确的答案。但你几乎不会以那种形式使用它。你将想要使用 SystemRandom(如果不可用,则回退),确保所需的字符集已表示,使用 Unicode(或不使用),确保连续调用产生唯一的字符串,使用一个字符串模块字符类的子集等。所有这些都需要比提供的答案中的代码多得多。各种试图推广解决方案的尝试都有局限性,而 strgen 则使用简单的模板语言具有更大的简洁性和表达力来解决这些问题。

它在 PyPI 上:

pip install StringGenerator

声明:我是strgen模块的作者。


请注意,如果安全方法不可用,则此操作将悄悄地回退到不安全的“random.Random”!当用户提供种子值时,它也会使用回退。当它使用加密安全方法时,不做任何一般性保证。 - xuiqzy

12

1
这很好,但它只使用“A-F”,而不是“A-Z”。当参数化“N”时,代码也会变得不那么好。 - Thomas Ahle

10

我觉得似乎还没有人回答这个问题呢,哈哈!但是,这里是我的回答:

import random

def random_alphanumeric(limit):
    #ascii alphabet of all alphanumerals
    r = (range(48, 58) + range(65, 91) + range(97, 123))
    random.shuffle(r)
    return reduce(lambda i, s: i + chr(s), r[:random.randint(0, len(r))], "")

5
我不会将其投下去,但我认为这对于如此简单的任务来说过于复杂了。返回表达式是一个怪物。简洁胜于繁琐。 - Carl Smith
16
@CarlSmith,我承认我的解决方案可能有些过于复杂,但我也知道有其他更简单的解决方案,只是希望找到一条不同的路径来得出一个好的答案。没有自由,创造力就会受到威胁,因此我还是决定发表了我的解决方案。 - JWL

9

如果你需要一个随机字符串而不是伪随机字符串,你应该使用os.urandom作为源。

from os import urandom
from itertools import islice, imap, repeat
import string

def rand_string(length=5):
    chars = set(string.ascii_uppercase + string.digits)
    char_gen = (c for c in imap(urandom, repeat(1)) if c in chars)
    return ''.join(islice(char_gen, None, length))

3
os.urandom 如何不是伪随机数?它可能使用更好的算法生成更随机的数字,但它仍然是伪随机的。 - Tyilo
1
@Tyilo,我知道/dev/random/dev/urandom之间的区别。问题在于当熵不足时,/dev/random会阻塞,这限制了它的实用性。对于一次性密码本,/dev/urandom并不够好,但我认为它比伪随机更好。 - John La Rooy
1
我认为/dev/random/dev/urandom都是伪随机数生成器,但这可能取决于你的定义。 - Tyilo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接