如何在Python中生成可以重复的随机UUID(使用种子)

82
Python的模块uuid中的uuid4()函数生成随机UUID,似乎每次生成的UUID都不同:

uuid4() 函数位于Python的uuid模块中,可生成随机UUID,并且每次生成的UUID似乎都不同:

In [1]: import uuid

In [2]: uuid.uuid4()
Out[2]: UUID('f6c9ad6c-eea0-4049-a7c5-56253bc3e9c0')

In [3]: uuid.uuid4()
Out[3]: UUID('2fc1b6f9-9052-4564-9be0-777e790af58f')

我希望能在每次运行脚本时生成相同的随机UUID,也就是说,我希望在uuid4()中种子化随机生成器。有没有办法做到这一点?(或通过其他方式实现此目的)?

到目前为止我尝试了什么

我使用带有随机128位整数(来自于种子实例random.Random())作为输入使用uuid.UUID()方法生成UUID:

import uuid
import random

rd = random.Random()
rd.seed(0)
uuid.UUID(rd.getrandbits(128))

然而,UUID() 似乎不接受此输入:

Traceback (most recent call last):
  File "uuid_gen_seed.py", line 6, in <module>
    uuid.UUID(rd.getrandbits(128))
  File "/usr/lib/python2.7/uuid.py", line 133, in __init__
    hex = hex.replace('urn:', '').replace('uuid:', '')
AttributeError: 'long' object has no attribute 'replace'

还有其他的建议吗?


它显然期望某种字符串,提到 hex 暗示你可以通过调用 hey(rd.getrandbits(128)) 来获得它。然而,你最终不会得到一个 uuid4。 - L3viathan
你需要一个方法来生成一个随机的小写十六进制数字。你还需要一个第二个方法来从{8, 9, a, b}中随机选择一个。将这两个方法按照正确的顺序组合起来,再加上字符'-'和'4',就可以创建你自己的UUID4方法。 - rossum
9个回答

55

快要到了:

uuid.UUID(int=rd.getrandbits(128))

这是在help的帮助下确定的:

>>> help(uuid.UUID.__init__)
Help on method __init__ in module uuid:

__init__(self, hex=None, bytes=None, bytes_le=None, fields=None, int=None, version=None) unbound uuid.UUID method
    Create a UUID from either a string of 32 hexadecimal digits,
    a string of 16 bytes as the 'bytes' argument, a string of 16 bytes
    in little-endian order as the 'bytes_le' argument, a tuple of six
    integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version,
    8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as
    the 'fields' argument, or a single 128-bit integer as the 'int'
    argument.  When a string of hex digits is given, curly braces,
    hyphens, and a URN prefix are all optional.  For example, these
    expressions all yield the same UUID:

    UUID('{12345678-1234-5678-1234-567812345678}')
    UUID('12345678123456781234567812345678')
    UUID('urn:uuid:12345678-1234-5678-1234-567812345678')
    UUID(bytes='\x12\x34\x56\x78'*4)
    UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' +
                  '\x12\x34\x56\x78\x12\x34\x56\x78')
    UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678))
    UUID(int=0x12345678123456781234567812345678)

    Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must
    be given.  The 'version' argument is optional; if given, the resulting
    UUID will have its variant and version set according to RFC 4122,
    overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'.

6
这个方法可行,但不一定会返回UUID4(详情请参见这里)。 - L3viathan
@L3viathan 我不明白为什么不行,你能解释一下吗? - Alex Hall
8
UUID4并非完全随机,其中部分位数是固定的。请查看维基百科文章中的相关章节。我不是在说这对问题提出者一定有影响,但我认为有必要澄清这一点,因为他们提到了UUID4。 - L3viathan
@L3viathan如何修改UUID输出,使其成为有效(确定性)的UUID4? - pir
9
只需添加 version 参数即可:uuid.UUID(int=rd.getrandbits(128), version=4) - L3viathan

29

Faker的存在使得这个变得非常容易。

>>> from faker import Faker
>>> f1 = Faker()
>>> f1.seed(4321)
>>> print(f1.uuid4())
cc733c92-6853-15f6-0e49-bec741188ebb
>>> print(f1.uuid4())
a41f020c-2d4d-333f-f1d3-979f1043fae0
>>> f1.seed(4321)
>>> print(f1.uuid4())
cc733c92-6853-15f6-0e49-bec741188ebb

1
类型错误:在实例上调用.seed()已被弃用。请改用类方法Faker.seed() - gurel_kaynak
值得澄清的是,Faker.uuid4 使用的是 uuid.UUID(int=rd.getrandbits(128), version=4)。链接在这里:https://github.com/pytest-dev/pytest-randomly/blob/main/src/pytest_randomly/__init__.py。 - skeller88

21

这是基于一个解决方案的使用,此处使用:

import hashlib
import uuid

m = hashlib.md5()
m.update(seed.encode('utf-8'))
new_uuid = uuid.UUID(m.hexdigest())

5
这里的“seed”是什么意思? - baxx
"seed" 是一个字符串。 - Diego Fernández Durán

11

由于还没有发布生成一致的版本4 UUID的直接解决方案:

import random
import uuid

rnd = random.Random()
rnd.seed(123) # NOTE: Of course don't use a static seed in production

random_uuid = uuid.UUID(int=rnd.getrandbits(128), version=4)

你可以在这里看到它们:

>>> random_uuid.version
4

这不仅是对版本信息的“模拟”,它创建了一个正确的UUIDv4:
引用: 如果给出version参数,则生成的UUID将根据RFC 4122设置其变体和版本号,覆盖给定十六进制、字节、bytes_le、fields或int中的位。 Python 3.8文档

愚蠢的Python命名/作用域。但很可能您不希望将已导入的uuid覆盖为uuid变量。 - Scheintod

8
如果有人需要在代码中添加种子UUID的话,可以采用以下方式进行猴子补丁。我的代码使用了uuid.uuid4(),但是为了测试,我希望UUID能够保持一致。下面的代码就是我实现这一目标的方法:
import uuid
import random

# -------------------------------------------
# Remove this block to generate different
# UUIDs everytime you run this code.
# This block should be right below the uuid
# import.
rd = random.Random()
rd.seed(0)
uuid.uuid4 = lambda: uuid.UUID(int=rd.getrandbits(128))
# -------------------------------------------

# Then normal code:

print(uuid.uuid4().hex)
print(uuid.uuid4().hex)
print(uuid.uuid4().hex)
print(uuid.uuid4().hex)

6

基于@user10229295的回答,提供一个简单的解决方案,并附上关于种子的评论。由于编辑队列已满,因此我开了一个新的答案:

import hashlib
import uuid

seed = 'Type your seed_string here' #Read comment below

m = hashlib.md5()
m.update(seed.encode('utf-8'))
new_uuid = uuid.UUID(m.hexdigest())

关于字符串'seed'的注释:它将成为生成UUID的种子:从同样的种子字符串中将总是生成相同的UUID。您可以将一些有意义的整数转换为字符串,连接不同的字符串并使用结果作为您的种子。通过这种方式,您将能够控制生成的UUID,这意味着您将能够知道使用的种子来重现您的UUID:使用相同的种子,从中生成的UUID将是相同的。


4

基于Alex的解决方案,以下内容将提供一个合适的UUID4:

random.seed(123210912)
a = "%32x" % random.getrandbits(128)
rd = a[:12] + '4' + a[13:16] + 'a' + a[17:]
uuid4 = uuid.UUID(rd)

0

快速入门

如果您的目标是可重现的UUID,这里有一种简洁的方法

import uuid
seeded_uuid = uuid.UUID(bytes=b"z123456789101112") # 7a313233-3435-3637-3839-313031313132

这个内部是如何工作的?

使用二进制字符串允许几乎任何东西充当种子。您也可以使用另一种确定性哈希算法,将某些数据转换为 32 字节的字符串表示该数据。在 uuid 调用下面有更多复杂性,但在其核心,这是种子 uuid 调用的工作方式。

initial_seed = b"z123456789101112"

# use this function to validate initial seed
is_valid = lambda x: len(x) == 16 and isinstance(x, bytes)

# for each byte get its unicode int value, convert to hex and concatenate as string
hex_rep = "".join([f"{b:x}" for b in initial_seed]) # 7a313233343536373839313031313132

# for a uuid, storing an int representation unlocks O(1) comparision
int_rep = int(hex_rep, base=16)  # 162421256209101963464626711665304482098

# string representation for readability
str_rep = f"\
{hex_rep[0:8]}-\
{hex_rep[8:12]}-\
{hex_rep[12:16]}-\
{hex_rep[16:20]}-\
{hex_rep[20:]}"  # 7a313233-3435-3637-3839-313031313132


0

如其他答案所示,UUID可以直接从种子生成。然而,如果种子不唯一(≈密码学安全),则存在非常小的可能性导致生成的UUID不唯一,因为其他应用程序可以从相同的种子生成UUID。

我建议将种子与秘密组合在一起。这可以通过HMAC实现。以下是一个实际示例:

import hmac
from hashlib import sha256
from uuid import UUID

secret = b'my-unique-secret'
seed = b'whatever'
h = hmac.new(secret, msg=seed, digestmod=sha256)
# digest should be truncated to 128 bits = 16 bytes
UUID(bytes=h.digest()[:16], version=4)

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