ZetCode

Python uuid

last modified October 29, 2026 Python 3.15

In this article we show how to generate and work with universally unique identifiers using the uuid module.

A UUID (universally unique identifier) is a 128-bit number used to identify objects without a central authority. Two systems can create UUIDs independently and, for practical purposes, never produce the same value. UUIDs are used as database keys, request and trace identifiers, file names, and message identifiers.

A UUID is usually written as 32 hexadecimal digits in five groups separated by hyphens, for example 6ba7b810-9dad-11d1-80b4-00c04fd430c8. The format and the generation methods are defined in RFC 9562, which replaced RFC 4122 in 2024. Four bits of every UUID hold its version, the method used to create it, and two or three bits hold the variant, the layout of the remaining bits.

VersionFunctionBased on
1uuid1time and MAC address
3uuid3MD5 hash of a namespace and a name
4uuid4random numbers
5uuid5SHA-1 hash of a namespace and a name
6uuid6time and node, reordered for sorting (3.14)
7uuid7Unix time in milliseconds and random bits (3.14)
8uuid8custom data (3.14)

Versions 6, 7, and 8 and the NIL and MAX constants were added in Python 3.14.

A random UUID

The uuid4 function returns a random UUID. It is the most common choice when we need a unique value and nothing else.

first_uuid.py
import uuid

u = uuid.uuid4()

print(u)
print(repr(u))
print(u.version)
print(u.variant)
print(u.hex)
print(u.int)
print(u.bytes)
print(u.urn)

The function returns a UUID object. Converting it to a string gives the standard hyphenated form.

print(u.hex)
print(u.int)
print(u.bytes)
print(u.urn)

The object offers several representations: 32 hexadecimal digits, a 128-bit integer, 16 bytes in big-endian order, and a URN. The version and variant attributes tell how the UUID was created. The variant string still refers to RFC 4122, because RFC 9562 uses the same variant.

$ python first_uuid.py
00580c74-5b62-4ffc-a6d3-5c8942e69d47
UUID('00580c74-5b62-4ffc-a6d3-5c8942e69d47')
4
specified in RFC 4122
00580c745b624ffca6d35c8942e69d47
457174731215148657636273659924880711
b'\x00X\x0ct[bO\xfc\xa6\xd3\\\x89B\xe6\x9dG'
urn:uuid:00580c74-5b62-4ffc-a6d3-5c8942e69d47

How unique is random

A version 4 UUID has 122 random bits; the other six bits encode the version and variant.

random_uuids.py
import uuid

ids = [uuid.uuid4() for _ in range(5)]
for u in ids:
    print(u, u.version)

print(len({uuid.uuid4() for _ in range(100_000)}))
print(f'{2 ** 122:,}')

We generate 100,000 UUIDs and put them in a set; none of them repeat. There are 2122 possible values. Even after generating a billion UUIDs per second for 85 years, the chance of a single collision would be about 50%. The uuid4 function takes its bits from os.urandom, so the values are also unpredictable.

$ python random_uuids.py
4f172344-f989-4981-bdd1-38eb2e458197 4
02eb18c1-3738-4459-9faa-ec584651780c 4
5639f75c-a403-4938-b4b6-de11e8eed6d4 4
679bc964-354c-4412-956b-d64c99f72be9 4
df770309-5709-4014-af3b-70c5bc8f3f95 4
100000
5,316,911,983,139,663,491,615,228,241,121,378,304

Parsing UUIDs

The UUID constructor accepts a string in several formats, or the bytes, int, fields, and other keyword arguments.

parse_uuid.py
import uuid

forms = [
    '6ba7b810-9dad-11d1-80b4-00c04fd430c8',
    '{6BA7B810-9DAD-11D1-80B4-00C04FD430C8}',
    'urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8',
    '6ba7b8109dad11d180b400c04fd430c8',
]

values = {uuid.UUID(s) for s in forms}
print(values)

u = uuid.UUID(bytes=bytes(range(16)))
print(u)
print(uuid.UUID(int=1))

try:
    uuid.UUID('6ba7b810-9dad-11d1-80b4')
except ValueError as e:
    print('ValueError:', e)

The constructor ignores braces, the urn:uuid: prefix, hyphens, and the case of the digits. All four strings give the same UUID, so the set contains a single element.

u = uuid.UUID(bytes=bytes(range(16)))
print(uuid.UUID(int=1))

We create UUIDs from 16 bytes and from an integer. A string with the wrong number of digits raises a ValueError, which makes the constructor useful for validating input.

$ python parse_uuid.py
{UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')}
00010203-0405-0607-0809-0a0b0c0d0e0f
00000000-0000-0000-0000-000000000001
ValueError: badly formed hexadecimal UUID string

Fields

The fields attribute splits a UUID into the six fields of the original version 1 layout.

fields.py
import uuid

u = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')

print(u.fields)
print(f'time_low            {u.time_low:08x}')
print(f'time_mid            {u.time_mid:04x}')
print(f'time_hi_version     {u.time_hi_version:04x}')
print(f'clock_seq_hi_variant {u.clock_seq_hi_variant:02x}')
print(f'clock_seq_low       {u.clock_seq_low:02x}')
print(f'node                {u.node:012x}')
print(u.version)

The UUID used here is NAMESPACE_DNS, a version 1 UUID. In version 1, the first three fields hold parts of a timestamp, the fourth and fifth hold the clock sequence and the variant, and the last one the node, usually a MAC address. For other versions, the names of the fields no longer describe their content, but the attributes still work.

$ python fields.py
(1806153744, 40365, 4561, 128, 180, 825973027016)
time_low            6ba7b810
time_mid            9dad
time_hi_version     11d1
clock_seq_hi_variant 80
clock_seq_low       b4
node                00c04fd430c8
1

Time-based UUIDs

The uuid1 function combines the current time, a clock sequence, and a node identifier.

time_based.py
import secrets
import uuid
from datetime import datetime, timedelta, UTC

node = secrets.randbits(48) | (1 << 40)
u = uuid.uuid1(node=node)
print(u, u.version)

epoch = datetime(1582, 10, 15, tzinfo=UTC)
created = epoch + timedelta(microseconds=u.time // 10)
print(created)

print(f'{u.node:012x}')
print(u.clock_seq)

By default, the node is the MAC address of a network interface, returned by uuid.getnode. Every UUID then reveals which machine created it and when, which is a privacy problem. In this example, we pass a random node instead.

node = secrets.randbits(48) | (1 << 40)

RFC 9562 recommends setting the multicast bit of a random node, so that it cannot clash with a real MAC address.

epoch = datetime(1582, 10, 15, tzinfo=UTC)
created = epoch + timedelta(microseconds=u.time // 10)

The time attribute is the number of 100-nanosecond intervals since 15 October 1582, the start of the Gregorian calendar. We convert it to a datetime. The clock sequence is a random 14-bit number that protects against duplicates if the clock goes backwards.

$ python time_based.py
762d9e38-d3e1-11f1-a975-23537ff974c0 1
2026-10-29 21:41:12.899948+00:00
23537ff974c0
10613

Name-based UUIDs

The uuid3 and uuid5 functions create a UUID from a namespace UUID and a name. The same input always gives the same UUID.

namespaced.py
import uuid

print(uuid.uuid5(uuid.NAMESPACE_DNS, 'zetcode.com'))
print(uuid.uuid5(uuid.NAMESPACE_DNS, 'zetcode.com'))
print(uuid.uuid5(uuid.NAMESPACE_URL, 'https://zetcode.com/python/'))
print(uuid.uuid3(uuid.NAMESPACE_DNS, 'zetcode.com'))

print(uuid.NAMESPACE_DNS)

products = uuid.uuid5(uuid.NAMESPACE_DNS, 'products.example.com')
for sku in ['KB-101', 'MS-220', 'KB-101']:
    print(sku, uuid.uuid5(products, sku))

Version 5 uses SHA-1 and version 3 uses MD5; prefer version 5. The module defines namespaces for domain names, URLs, ISO OIDs, and X.500 names.

products = uuid.uuid5(uuid.NAMESPACE_DNS, 'products.example.com')
for sku in ['KB-101', 'MS-220', 'KB-101']:
    print(sku, uuid.uuid5(products, sku))

Any UUID can be a namespace. We create a namespace for our product catalog and derive stable identifiers from product codes. Different systems that import the same product compute the same UUID without talking to each other.

$ python namespaced.py
881419e6-9386-5855-b0a9-1216bdd46c43
881419e6-9386-5855-b0a9-1216bdd46c43
2b0c3b38-d947-51ac-8f72-39229057f75c
c695cbd5-410f-358c-b204-ff4ee15e81d8
6ba7b810-9dad-11d1-80b4-00c04fd430c8
KB-101 409f8e28-3954-5903-b237-da8f90d90d41
MS-220 7d9942a6-a950-55d9-b99a-6af748903cef
KB-101 409f8e28-3954-5903-b237-da8f90d90d41

UUID version 7

A version 7 UUID starts with a 48-bit Unix timestamp in milliseconds, followed by a counter and random bits. Values created later sort after earlier ones.

uuid7_demo.py
import uuid
from datetime import datetime, UTC

u = uuid.uuid7()
print(u, u.version)

ms = u.int >> 80
print(ms)
print(datetime.fromtimestamp(ms / 1000, tz=UTC))

for _ in range(4):
    print(uuid.uuid7())

The top 48 bits of the 128-bit integer hold the timestamp, so we shift the integer right by 80 bits to get it.

ms = u.int >> 80
print(datetime.fromtimestamp(ms / 1000, tz=UTC))

The timestamp is visible to anyone who sees the UUID. Do not use version 7 when the creation time of a record must stay private.

for _ in range(4):
    print(uuid.uuid7())

UUIDs created within the same millisecond differ only in the counter and the random tail. Python's implementation increments the counter for each UUID in the same millisecond, so they still increase.

$ python uuid7_demo.py
01a1899c-205e-7195-b187-95c552f581d8 7
1793310072926
2026-10-29 21:41:12.926000+00:00
01a1899c-205e-7195-b187-95c655818385
01a1899c-205e-7195-b187-95c7be7ec982
01a1899c-205e-7195-b187-95c896b45cec
01a1899c-205e-7195-b187-95c9c3bd5e9d

Sort order

Random UUIDs scatter new rows across a database index. Time-ordered UUIDs are inserted at the end, like auto-incrementing integers, which keeps B-tree indexes compact.

sort_order.py
import uuid

v4 = [uuid.uuid4() for _ in range(1000)]
v7 = [uuid.uuid7() for _ in range(1000)]

print(v4 == sorted(v4))
print(v7 == sorted(v7))
print([str(u) for u in v7] == sorted(str(u) for u in v7))
print(len({u.int >> 80 for u in v7}), 'distinct milliseconds')

We generate 1000 UUIDs of versions 4 and 7 and check whether the creation order is the sorted order. For version 7, it is, both for the UUID objects and their strings. The 1000 UUIDs were created within a few milliseconds; the counter keeps them ordered.

$ python sort_order.py
False
True
True
2 distinct milliseconds

UUID version 6

Version 6 contains the same data as version 1, but the timestamp is stored with its most significant bits first.

uuid6_demo.py
import uuid

node = 0x0a0b0c0d0e0f
v1 = uuid.uuid1(node=node, clock_seq=0)
v6 = uuid.uuid6(node=node, clock_seq=0)

print(f'timestamp {v1.time:015x}')
print(f'uuid1     {v1}')
print(f'uuid6     {v6}')
print(v6.time - v1.time)

Version 1 starts with the lowest 32 bits of the timestamp, which change the fastest, so its values do not sort by time. Version 6 stores the timestamp in order and sorts correctly. RFC 9562 recommends version 7 for new systems; version 6 is meant for systems that already use version 1.

$ python uuid6_demo.py
timestamp 1f1d3e176365e2e
uuid1     76365e2e-d3e1-11f1-8000-0a0b0c0d0e0f
uuid6     1f1d3e17-6365-6e7d-8000-0a0b0c0d0e0f
79

UUID version 8

Version 8 lets applications put their own data into a UUID. The uuid8 function takes three blocks: 48 bits, 12 bits, and 62 bits.

uuid8_demo.py
import uuid

u = uuid.uuid8(a=0x123456789abc, b=0xdef, c=0x123456789abcdef)
print(u, u.version)

print(uuid.uuid8().version)

tenant = 42
shard = 7
seq = 1001
custom = uuid.uuid8(a=tenant, b=shard, c=seq)
print(custom)
print(custom.int >> 80, (custom.int >> 64) & 0xfff, custom.int & ((1 << 62) - 1))

The remaining six bits hold the version and variant. Blocks that are not given are filled with random bits, so uuid8() without arguments returns a random UUID.

custom = uuid.uuid8(a=tenant, b=shard, c=seq)

We encode a tenant, a shard, and a sequence number and extract them back with shifts and masks. Such UUIDs are unique only if the application makes sure the combinations are.

$ python uuid8_demo.py
12345678-9abc-8def-8123-456789abcdef 8
8
00000000-002a-8007-8000-0000000003e9
42 7 1001

NIL and MAX

The NIL UUID has all bits set to zero and the MAX UUID has all bits set to one.

nil_max.py
import uuid

print(uuid.NIL)
print(uuid.MAX)
print(uuid.NIL.int, uuid.MAX.int == 2 ** 128 - 1)

def find_user(user_id=uuid.NIL):
    if user_id == uuid.NIL:
        return 'anonymous'
    return f'user {user_id}'

print(find_user())
print(find_user(uuid.UUID('9c2f1b4e-8d3a-4f6b-a1c2-3e4d5f6a7b8c')))

The special values can be used as placeholders, for example to mark a missing identifier in a column that does not allow NULL, or as the lowest and highest bounds of a range.

$ python nil_max.py
00000000-0000-0000-0000-000000000000
ffffffff-ffff-ffff-ffff-ffffffffffff
0 True
anonymous
user 9c2f1b4e-8d3a-4f6b-a1c2-3e4d5f6a7b8c

Comparing UUIDs

UUID objects are compared by their integer values. They are immutable and hashable, so they can be used as dictionary keys and set members.

compare_uuids.py
import uuid

a = uuid.UUID('00000000-0000-0000-0000-000000000002')
b = uuid.UUID('00000000-0000-0000-0000-00000000000a')

print(a < b)
print(a == str(a))
print(str(a).upper() == str(a))
print(uuid.UUID(str(a).upper()) == a)

users = {a: 'jane', b: 'john'}
print(users[uuid.UUID('00000000000000000000000000000002')])

A UUID is never equal to a string, even to its own string form. Strings in upper case or without hyphens differ from the canonical form, too. Convert input to UUID objects before comparing or looking up values.

$ python compare_uuids.py
True
False
True
True
jane

UUIDs in JSON

The json module does not know how to serialize UUID objects.

json_uuid.py
import json
import uuid

order = {'id': uuid.uuid7(), 'items': 3}

try:
    json.dumps(order)
except TypeError as e:
    print('TypeError:', e)

data = json.dumps(order, default=str)
print(data)

loaded = json.loads(data)
loaded['id'] = uuid.UUID(loaded['id'])
print(loaded)

The default function is called for objects that json cannot serialize; str turns a UUID into its canonical form. When loading, we convert the string back to a UUID.

$ python json_uuid.py
TypeError: Object of type UUID is not JSON serializable
{"id": "01a1899c-20b4-7367-9ad4-2af79f5c7323", "items": 3}
{'id': UUID('01a1899c-20b4-7367-9ad4-2af79f5c7323'), 'items': 3}

Short identifiers

The canonical form has 36 characters. Encoding the 16 bytes with URL-safe Base64 gives a 22-character string, which is handy in URLs.

short_ids.py
import base64
import uuid

def short(u):
    return base64.urlsafe_b64encode(u.bytes).rstrip(b'=').decode()

def expand(s):
    return uuid.UUID(bytes=base64.urlsafe_b64decode(s + '=='))

u = uuid.uuid4()
s = short(u)
print(u, len(str(u)))
print(s, len(s))
print(expand(s) == u)

We remove the two padding characters when encoding and add them back when decoding. The base64 article covers the encoding in more detail.

$ python short_ids.py
c99341e5-6888-48e8-8d2a-7d061f9ce994 36
yZNB5WiISOiNKn0GH5zplA 22
True

Storing UUIDs in SQLite

SQLite has no UUID type. We can store UUIDs as 16-byte BLOBs, which take less than half the space of strings.

sqlite_uuid.py
import sqlite3
import uuid

sqlite3.register_adapter(uuid.UUID, lambda u: u.bytes)
sqlite3.register_converter('UUID', lambda b: uuid.UUID(bytes=b))

con = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
con.execute('CREATE TABLE events (id UUID PRIMARY KEY, name TEXT)')

for name in ['created', 'paid', 'shipped', 'delivered']:
    con.execute('INSERT INTO events VALUES (?, ?)', (uuid.uuid7(), name))

for event_id, name in con.execute('SELECT id, name FROM events ORDER BY id'):
    print(type(event_id).__name__, event_id, name)

print(con.execute('SELECT length(id) FROM events LIMIT 1').fetchone())

An adapter converts UUID objects to bytes when they are passed as query parameters. A converter turns columns declared as UUID back into objects; it is enabled with detect_types=sqlite3.PARSE_DECLTYPES.

for event_id, name in con.execute('SELECT id, name FROM events ORDER BY id'):

Bytes are compared in big-endian order, the same order as the integers, so ordering by the version 7 key returns the events in the order they were created.

$ python sqlite_uuid.py
UUID 01a1899c-20d2-7165-915d-93750a518033 created
UUID 01a1899c-20d2-7165-915d-9376bc1005f3 paid
UUID 01a1899c-20d2-7165-915d-937768f7daf8 shipped
UUID 01a1899c-20d2-7165-915d-9378e1b6966e delivered
(16,)

Command line interface

The module can be run as a script to generate UUIDs from the command line. Without options, the command prints a version 4 UUID. The -u option selects the function and the -C option, added in Python 3.14, generates several UUIDs at once. For versions 3 and 5, -n gives the namespace, either a UUID or one of the predefined names @dns, @url, @oid, and @x500, and -N the name.

$ python -m uuid
56c3bf19-e473-4a7e-bced-d4dddfc0785c
$ python -m uuid -u uuid7 -C 3
01a1899c-20fd-749e-bb61-b67efddedee2
01a1899c-20fd-749e-bb61-b67fc380e8ee
01a1899c-20fd-749e-bb61-b680720622bf
$ python -m uuid -u uuid5 -n @dns -N zetcode.com
881419e6-9386-5855-b0a9-1216bdd46c43
$ python -m uuid -u uuid3 -n @url -N https://zetcode.com/
db5b3474-1a73-3515-9f97-61be00de4531

Source

uuid – UUID objects according to RFC 9562

RFC 9562 – Universally Unique IDentifiers (UUIDs)

In this article we have generated and used UUIDs with the Python uuid module.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.