在Python/Django中生成唯一字符串

33

我想在我的网站上为用户生成一个大小为5的字符串(密钥),类似于BBM PIN。

该密钥将包含数字和大写英文字母:

  • AU1B7
  • Y56AX
  • M0K7A

即使我生成数百万个字符串,我也希望能够放心地保证其唯一性。

请问最Pythonic的方法是什么?


1
base64.b32encode('12345') == 'GEZDGNBV' - Paulo Scardine
1
请参阅 https://github.com/jbittel/base32-crockford。 - Paulo Scardine
2
您可以使用 django.utils.crypto.get_random_string(5, string.ascii_uppercase+string.digits) 生成它们。您可能希望限制字符集,以避免生成可能在某些字体中难以辨认的混淆字符串,例如 l1I1l。唯一性需要您持久化分配的字符串集合。 - mhawke
1
你不能同时拥有“随机”和“唯一”。请将“随机”替换为“看起来随机”。 - Ignacio Vazquez-Abrams
3
因为我是梦想的破坏者,痛苦和绝望的带来者。绝望和痛苦。 - Ignacio Vazquez-Abrams
显示剩余3条评论
12个回答

70

2
代码长度+1。但我很好奇,你如何确保生成的值是唯一的?它不会生成重复的值吗? - d-coder
当然会有冲突!http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates UUID越长,发生冲突的概率就越小。基本上,需要一些进一步的逻辑来生成和存储它们,这完全取决于你如何进行操作。 - zzart
11
这不会生成AU1B7、Y56AX或M0K7A这样的字母,因为alpha字符被限制在A到F之间。它也不能生成保证唯一的值。您可以添加代码来展示如何处理冲突。 - mhawke
1
你可以像这个 Stack Overflow 的问题中那样处理碰撞。 - gatlanticus
1
这会生成一个十六进制字符串。你实际上不能用它生成数百万个,但你可以生成一百万个。 - Caveman

21

2
这会生成一个十六进制字符串。你实际上不能用它生成数百万个,但你可以生成一百万个。 - Caveman

20

更安全且更简短的方法是使用Django的加密模块。

from django.utils.crypto import get_random_string
code = get_random_string(5)

get_random_string()函数返回一个安全生成的随机字符串,底层使用secrets模块。

您还可以传递allowed_chars

from django.utils.crypto import get_random_string
import string

code = get_random_string(5, allowed_chars=string.ascii_uppercase + string.digits)

4

我不确定是否存在任何简洁的加密方式,但是可以使用一个简单直接的函数来实现,假设你将所有生成的字符串保存在一个集合中:

import random

def generate(unique):
    chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
    while True:
        value = "".join(random.choice(chars) for _ in range(5))
        if value not in unique:
            unique.add(value)
            break

unique = set()
for _ in range(10):
    generate(unique)

1
只是强调一下,在Python的secret包页面上已经说明:“特别是,应该优先使用secrets而不是随机模块中默认的伪随机数生成器,因为后者是为建模和仿真而设计的,而不是为安全或加密而设计的。” - Shadi
点赞使用Python集合来存储唯一元素的集合。 - Caveman

4
如果您可以承受在生成的数字中失去“8”和“9”,那么有一种非常Pythonic的解决方案可以获得随机数。
import os
import base64

base64.b32encode(os.urandom(3))[:5].decode('utf-8')

如果你想要独一无二的结果,那么你会遇到问题。因为 36 * 36 * 36 * 36 * 36 = 60'466'176,如果有几百万个数据,肯定会产生冲突。由于集合比字典快,我们可以使用集合...

some_set = set()

def generate():
    return base64.b32encode(os.urandom(3))[:5].decode('utf-8')

def generate_unique():
    string = generate()
    while string in some_set:
        string = generate()
    some_set.add(string)
    return string

但是由于唯一性通常更加重要,我建议为数字从0到36^5 - 1 中的每个数字生成一个唯一的代码,如下所示。我们可以使用大质数和模数来生成伪随机数。


import base64
import math

num = 1
prime_number = 60466181
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789'

def num_to_code(n: int):
    string = ''
    hashed = hash_number(n)
    for x in range(5):
        charnumber = hashed % 36
        hashed = math.floor(hashed / 36)
        string += characters[charnumber]
    return string

def hash_number(n: int, rounds = 20):
    if rounds <= 0:
        return n
    hashed = (n * prime_number) % (36 ** 5)
    return hash_number(hashed, rounds - 1)

if __name__ == '__main__':
    code = num_to_code(1)
    print(code)

以下是生成0-5的结果,它们始终会生成相同的序列。
0 AAAAA (easily fixable ofc)
1 ZGQR9
2 ON797
3 DUMQ6
4 31384
5 R8IP3

3
如果您有一种将每个用户与唯一ID关联的方法(例如Django或Flask中的Primary Key),则可以执行以下操作:

注意: 这不会生成固定长度。

我们将向右填充user_id,以使生成的长度更为静态。

import os
import base64

user_id = 1

#pad the string
number_generate = str(user_id).rjust(5,"0")

base64.b32encode(bytes(number_generate, 'utf-8')).decode('utf-8').replace('=','')

2
这种方法是使其“独特”的唯一方式,并且在我看来应该成为被接受的答案。 - because_im_batman

1
size = 5
''.join(random.choice(string.letters[26:] + string.digits) for in range(size))

这将生成一些短代码,但它们可以被复制。因此,在保存之前,请检查它们在您的数据库中是否唯一。
def generate(size=5):
    code = ''.join(random.choice(string.letters[26:] + string.digits) for in range(size))
    if check_if_duplicate(code):
        return generate(size=5)
    return code

或者使用django的唯一约束,并处理异常。

你能解释一下 string.lettersstring.digits 是什么吗? - Diansheng
1
他的意思是 string.ascii_letters - GLRoman

1

要生成唯一的代码,您可以使用以下命令:

import uuid 
str(uuid.uuid1())[:5]

1
在Django中有一个函数可以实现你想要的功能(感谢this answer)。

Django provides the function get_random_string() which will satisfy the alphanumeric string generation requirement. You don't need any extra package because it's in the django.utils.crypto module.

>>> from django.utils.crypto import get_random_string
>>> unique_id = get_random_string(length=32)
>>> unique_id
u'rRXVe68NO7m3mHoBS488KdHaqQPD6Ofv'

You can also vary the set of characters with allowed_chars:

>>> short_genome = get_random_string(length=32, allowed_chars='ACTG')
>>> short_genome
u'CCCAAAAGTACGTCCGGCATTTGTCCACCCCT'

1
我有很多模型中都有一个独特的字段,名为“systemCode”。我手动生成它,但有时也会从用户输入中获取值,所以在保存之前必须检查该值。如果匹配,则重新生成此值作为唯一值。
以下是我在此情况下生成唯一字符串的方法:

This is my standard class Model :

class ClassOne(models.Model):
   name = models.CharField(max_length=100)
   systemCode = models.CharField(max_length=25, blank=True, null=True, unique=True)
   ....

我正在使用save()方法来生成并检查这个systemCode是否唯一:
    def save(self, *args, **kwargs):
        systemCode = self.systemCode
        if not systemCode:
            systemCode = uuid.uuid4().hex[:6].upper()
        while ClassOne.objects.filter(systemCode=systemCode).exclude(pk=self.pk).exists():
            systemCode = uuid.uuid4().hex[:6].upper()
        self.systemCode = systemCode
        super(ClassOne, self).save(*args, **kwargs)

但是我所有的模型中都有相同的systemCode字段。因此,我正在使用一个函数来生成值。

因此,这就是如何使用saveSystemCode()函数为所有模型生成唯一值的方法:

import uuid 

def saveSystemCode(inClass, inCode, inPK, prefix):
    systemCode = inCode
    if not systemCode:
        systemCode = uuid.uuid4().hex[:6].upper()

    while inClass.objects.filter(systemCode=systemCode).exclude(pk=inPK).exists():
        systemCode = uuid.uuid4().hex[:6].upper()

    return systemCode

class ClassOne(models.Model):
    name = models.CharField(max_length=100)
    systemCode = models.CharField(max_length=25, blank=True, null=True, unique=True)
    ....

    def save(self, *args, **kwargs):
        self.systemCode = saveSystemCode(ClassOne, self.systemCode, self.pk, 'one_')
        super(ClassOne, self).save(*args, **kwargs)


class ClassTwo(models.Model):
    name = models.CharField(max_length=100)
    systemCode = models.CharField(max_length=25, blank=True, null=True, unique=True)
    ....

    def save(self, *args, **kwargs):
        self.systemCode = saveSystemCode(ClassTwo, self.systemCode, self.pk, 'two_')
        super(ClassTwo, self).save(*args, **kwargs)

class ClassThree(models.Model):
    name = models.CharField(max_length=100)
    systemCode = models.CharField(max_length=25, blank=True, null=True, unique=True)
    ....

    def save(self, *args, **kwargs):
        self.systemCode = saveSystemCode(ClassThree, self.systemCode, self.pk, 'three_')
        super(ClassThree, self).save(*args, **kwargs)

while 循环在 'saveSystemCode' 函数中防止再次保存相同的值。


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