Генерація UUID v4 у Python — uuid.uuid4()

·Backend Developer·ПеревіреноDmitri Volkov·Опубліковано

Використовуйте безкоштовний UUID v4 Generator прямо в браузері — без встановлення.

Спробувати UUID v4 Generator онлайн →

Щоразу, коли мені потрібен стійкий до колізій ідентифікатор для рядка бази даних, трасування API або токена сесії, відповідь одна: згенерувати UUID v4 у Python — один рядок, нуль залежностей: uuid.uuid4(). Вбудований модуль Python uuid використовує os.urandom() для криптографічно безпечної випадковості. Якщо потрібен UUID без написання коду, скористайтесь онлайн-генератором UUID v4 — він працює миттєво. Цей посібник охоплює атрибути об'єкта UUID, масову генерацію, серіалізацію JSON, зберігання у базі даних, валідацію, uuid-utils (~10× швидший замінник на базі Rust) та чотири найпоширеніші помилки — усе для Python 3.8+.

Ключові висновки
  • uuid.uuid4() вбудований у стандартну бібліотеку Python — достатньо import uuid, встановлення через pip не потрібне.
  • Результат — об'єкт uuid.UUID, а не рядок — використовуйте str(), .hex або .bytes, щоб обрати потрібне представлення для вашого рівня зберігання.
  • UUID v4 використовує 122 випадкових біти з os.urandom() — криптографічно безпечний, без розкриття MAC-адреси або мітки часу.
  • Для сервісів із високою пропускною здатністю pip install uuid-utils є повним замінником, який працює ~10 разів швидше завдяки Rust.
  • Ніколи не передавайте uuid.uuid4 (без дужок) як аргумент за замовчуванням безпосередньо в датаклас або модель Pydantic — усі екземпляри отримають один і той самий UUID.

Що таке UUID v4?

UUID (Universally Unique Identifier, Універсально унікальний ідентифікатор) — це 128-бітна мітка, відформатована як 32 шістнадцяткових символи, розділені на п'ять груп дефісами: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Версія 4 є найпоширенішим варіантом: 122 з 128 біт генеруються випадково, а решта 6 біт кодують версію (4) та варіант (RFC 4122). Тут немає мітки часу та ідентифікатора хоста — ідентифікатор повністю непрозорий і не розкриває конфіденційних даних. Імовірність колізії двох незалежно згенерованих UUID v4 настільки мала, що на практиці це ніколи не трапляється, навіть у розподілених системах, що генерують мільйони ідентифікаторів на секунду.

Стандарт RFC 4122 визначає п'ять версій UUID. Версія 1 поєднує мітку часу та MAC-адресу мережевого інтерфейсу — це розкриває конфіденційні відомості про хост і порядок генерації. Версії 3 та 5 є детермінованими: вони хешують заданий простір імен разом із назвою, тому один і той самий вхід завжди дає один і той самий UUID. Версія 4 — найпростіша і найбільш конфіденційна: жодного залежного від хоста матеріалу, жодної мітки часу у самому значенні, лише криптографічно захищена псевдовипадкова ентропія. Саме тому це стандартний вибір для первинних ключів бази даних, токенів сесій, ідентифікаторів трасування та будь-якого місця, де ідентифікатор не повинен розкривати час свого створення чи машину, що його згенерувала.

UUID v4 є кращим вибором, ніж цілочислові автоінкрементні ідентифікатори у ряді сценаріїв. По-перше, UUID v4 не розкриває обсяг даних: якщо клієнт бачить ідентифікатор order_id=1042, він легко здогадується, скільки замовлень у системі. По-друге, UUID v4 можна згенерувати на стороні клієнта або в будь-якому мікросервісі без координації з базою даних — немає потреби робити запит до БД просто щоб отримати наступний ідентифікатор. По-третє, UUID v4 ідеально підходять для розподілених систем, де кілька вузлів вставляють рядки паралельно: ймовірність колізії двох незалежно згенерованих UUID v4 менша, ніж ймовірність падіння метеорита на дата-центр. Єдиний реальний недолік — розмір (16 байт проти 4-8 байт для int) та відсутність природного сортування за часом, що можна компенсувати UUID v7, якщо упорядкованість індексу є важливою вимогою.

Before · Python
After · Python
event_id = "evt-" + str(random.randint(100000, 999999))  # fragile, not unique
event_id = str(uuid.uuid4())  # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e

uuid.uuid4() — стандартний спосіб генерації UUID v4 у Python

Модуль uuid є частиною стандартної бібліотеки Python. Виклик uuid.uuid4() повертає об'єкт uuid.UUID з повним набором атрибутів для різних представлень. Перетворення на рядок за допомогою str() дає канонічний формат із дефісами, якого очікують API, бази даних та HTTP-заголовки.

Python 3.8+ — мінімальний приклад
import uuid

# Генерація UUID v4
request_id = uuid.uuid4()

print(request_id)           # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e
print(type(request_id))     # <class 'uuid.UUID'>
print(request_id.version)   # 4

# Перетворення на рядок для JSON / HTTP-заголовків
print(str(request_id))      # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"
print(request_id.hex)       # "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" (без дефісів)
print(request_id.bytes)     # b';...' (16 сирих байт)

Поширений реальний шаблон — прикріплення UUID до кожного вихідного запиту API, щоб можна було корелювати журнали між сервісами. Ось мінімальна обгортка сесії requests, яка додає новий UUID до кожного виклику:

Python 3.8+ — ідентифікатор трасування для кожного запиту
import uuid
import requests

def call_api(endpoint: str, payload: dict) -> dict:
    trace_id = str(uuid.uuid4())
    headers = {
        "X-Request-ID": trace_id,
        "Content-Type": "application/json",
    }
    response = requests.post(endpoint, json=payload, headers=headers, timeout=10)
    response.raise_for_status()
    return {"trace_id": trace_id, "data": response.json()}

# result["trace_id"] дозволяє знайти точний запит у журналах усіх сервісів
result = call_api("https://api.example.com/v1/orders", {"product_id": "prod_7x2k", "qty": 3})
print(result["trace_id"])  # e.g. "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"

Для масової генерації UUID — наприклад, попереднього заповнення пакету рядків бази даних — генератор списків є ідіоматичним і читабельним підходом:

Python 3.8+ — масова генерація
import uuid

# Попередня генерація ідентифікаторів для 1000 подій телеметрії
event_ids = [str(uuid.uuid4()) for _ in range(1000)]
print(f"Generated {len(event_ids)} unique IDs")
print(event_ids[0])   # e.g. "a1c2e3f4-..."
print(event_ids[-1])  # different value every time

Для роботи з дуже великими обсягами UUID ефективніше використовувати генератор замість списку — він не завантажує всі значення одночасно в пам'ять:

Python 3.8+ — генератор UUID для потокової обробки
import uuid

def uuid4_stream():
    """Нескінченний генератор рядків UUID v4."""
    while True:
        yield str(uuid.uuid4())

# Обробка 1 мільйона UUID без завантаження всіх у пам'ять
gen = uuid4_stream()
for i, uid in enumerate(gen):
    process_event(uid)
    if i >= 999_999:
        break

Такий підхід особливо корисний при потоковій передачі великих масивів подій до Kafka або S3, де кожен запис отримує унікальний ідентифікатор, але зберігання мільйона рядків у списку є надлишковим для пам'яті.

Потрібен швидкий UUID без запуску коду? Скористайтесь онлайн-генератором UUID v4 — скопіюйте нове значення одним кліком або згенеруйте сотні одразу. Це зручно для заповнення тестових баз даних або підготовки файлів з тестовими даними.

UUID v4 є безпечним для використання у багатопотокових та асинхронних програмах. Кожен виклик uuid.uuid4() читає свіжі байти з os.urandom(), що є атомарною операцією — немає спільного стану між потоками, жодних умов гонки, жодного глобального лічильника для синхронізації. У коді asyncio ви можете викликати uuid.uuid4() безпосередньо у корутині без await — це синхронна, негативно-не-блокуюча операція, яка виконується за мікросекунди.

Примітка:uuid.uuid4() всередині викликає os.urandom(16), потім встановлює біти 6–7 байту 8 у 10 (варіант) та біти 12–15 байту 6 у 0100 (версія 4). Решта 122 біти є випадковими. Саме тому не можна довіряти версії, не розібравши рядок за допомогою uuid.UUID().

Атрибути та представлення об'єкта UUID

Об'єкт uuid.UUID надає кілька представлень одного і того самого 128-бітного значення. Вибір правильного представлення для вашого рівня зберігання запобігає непомітному пошкодженню даних та марній витраті байтів.

Атрибут / Метод
Тип
Опис
uuid.UUID(hex=...)
UUID
Розбирає існуючий UUID з рядка у форматі hex — з дефісами або без.
.hex
str
32-символьний рядок у нижньому регістрі без дефісів — компактний формат зберігання.
.int
int
128-бітне ціле число — зручне для арифметики та сортування.
.bytes
bytes
16-байтне двійкове представлення у форматі big-endian — найефективніший розмір зберігання.
.bytes_le
bytes
16-байтне двійкове представлення у форматі little-endian — відповідає порядку байтів GUID від Microsoft.
.fields
tuple
Кортеж із шести полів UUID: (time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node).
.version
int | None
Номер версії UUID (1–5, або None для нестандартних UUID).
.variant
str
Рядок варіанту UUID — "specified in RFC 4122" для стандартних UUID.
str(uuid_obj)
str
Канонічний 36-символьний рядок із чотирма дефісами: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
Python 3.8+ — усі представлення
import uuid

u = uuid.uuid4()

print(str(u))         # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"  (36 символів)
print(u.hex)          # "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e"      (32 символи, без дефісів)
print(u.bytes)        # b';Š...'                       (16 байт, big-endian)
print(u.bytes_le)     # b'Š...'                       (16 байт, little-endian)
print(u.int)          # 78823... (128-бітне ціле число)
print(u.version)      # 4
print(u.variant)      # 'specified in RFC 4122'

# Зворотне перетворення: відновлення з рядка
reconstructed = uuid.UUID("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e")
print(reconstructed == u)  # True (if u was that value)

Для PostgreSQL з psycopg2 або asyncpg передавайте об'єкт UUID напряму — драйвер обробляє відображення на рідний тип стовпця uuid. Для SQLite використовуйте str(u) (TEXT) або u.bytes (BLOB, 16 байт проти 36 для рядка). При масштабному зберіганні .bytes займає на 55% менше місця, ніж канонічний рядок.

Атрибут .int особливо корисний, коли потрібно зберегти UUID як числовий первинний ключ у базах даних, що не мають рідного типу UUID, або при реалізації хешування та сегментування (sharding). Атрибут .fields розкриває шість компонентів RFC 4122 окремо — корисно при написанні власного декодера або міграційного скрипту, що перевіряє версію та варіант UUID у старій базі даних. Пам'ятайте, що два UUID однакового значення завжди матимуть u1 == u2 як True, незалежно від того, яке представлення ви використовуєте для порівняння.

Валідація та розбір рядків UUID v4 у Python

Щоразу, коли UUID надходить із введення користувача, параметра шляху URL або стороннього API, його слід перевірити перед використанням як ключа бази даних. Ідіоматичний підхід — спроба конструювання за допомогою uuid.UUID() з перехопленням ValueError. Також можна перевірити, що вхідне значення є саме версією 4, перевіривши атрибут .version.

Python 3.8+ — допоміжна функція валідації
import uuid

def parse_uuid4(raw: str) -> uuid.UUID:
    """
    Parse and validate a UUID v4 string.
    Raises ValueError for invalid format or wrong version.
    """
    try:
        u = uuid.UUID(raw)
    except ValueError as exc:
        raise ValueError(f"Invalid UUID format: {raw!r}") from exc

    if u.version != 4:
        raise ValueError(f"Expected UUID v4, got v{u.version}: {raw!r}")

    return u

# Використання в обробнику маршруту FastAPI / Flask
def get_order(order_id: str):
    try:
        uid = parse_uuid4(order_id)
    except ValueError as exc:
        return {"error": str(exc)}, 400

    # тепер безпечно використовувати uid у запиті до БД
    return {"order_id": str(uid), "status": "processing"}
Примітка:uuid.UUID() приймає рядки як із дефісами, так і без, а також із префіксом urn:uuid:. Тому "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" (без дефісів) і "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" розбираються в один і той самий об'єкт.

Деякі розробники вдаються до регулярних виразів для валідації UUID — наприклад, перевіряють відповідність рядка шаблону [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}. Цей підхід спрацьовує для версії 4, але є крихким: він відхиляє UUID у верхньому регістрі, URN-префіксовані рядки (urn:uuid:...) та UUID без дефісів — усі допустимі формати згідно з RFC 4122. Підхід на основі uuid.UUID() надійніший: стандартна бібліотека обробляє всі допустимі представлення, а явна перевірка .version виражає наміри значно зрозуміліше, ніж складний рядок regex. Ще одна перевага — при зміні вимог (наприклад, прийняти як v4, так і v7) достатньо змінити одну умову у перевірці версії, а не переписувати регулярний вираз.

З точки зору безпеки, завжди валідуйте UUID на вході, якщо він надходить із ненадійного джерела — навіть якщо ваша бізнес-логіка не розрізняє версії UUID. Зловмисник може надіслати рядок довільної форми замість UUID і сподіватися, що некоректний формат призведе до непередбаченої поведінки у вашому SQL-запиті або lookup-логіці. Простий виклик uuid.UUID(raw) у блоці try/except зупиняє будь-який рядок, що не є валідним 128-бітним UUID, перш ніж він потрапить до шару бази даних.

Якщо ви використовуєте FastAPI або Pydantic v2, валідація UUID вже вбудована: оголошення поля з типом uuid.UUID автоматично розбирає та перевіряє вхідний рядок, повертаючи HTTP 422 Unprocessable Entity, якщо значення не відповідає формату UUID. Це означає, що в обробниках FastAPI немає потреби писати власний блок try/except — валідація відбувається на рівні моделі до того, як виклик взагалі потрапляє до тіла функції. Для Flask, де цього немає з коробки, наведена функція parse_uuid4() є мінімальним рішенням. Ще одна практична порада: при логуванні UUID завжди нормалізуйте до канонічного рядкового формату str(u) — це спрощує grep через лог-файли та дозволяє вставляти значення безпосередньо у SQL-консоль без додаткових перетворень.

UUID v4 у JSON-відповідях і відповідях API

Стандарт JSON не має типу UUID — UUID у JSON завжди є рядком. Це означає, що необхідно перетворити об'єкт uuid.UUID на рядок перед передачею до json.dumps(). Найчистіший підхід — підклас JSONEncoder, щоб не розкидати виклики str() по всьому коду.

Python 3.8+ — власний JSONEncoder для UUID
import json
import uuid
from datetime import datetime

class ApiEncoder(json.JSONEncoder):
    """Serialize UUID and datetime objects in JSON responses."""
    def default(self, obj):
        if isinstance(obj, uuid.UUID):
            return str(obj)
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

# Реалістична відповідь API з вкладеними UUID
api_response = {
    "request_id": uuid.uuid4(),
    "created_at": datetime(2026, 3, 14, 9, 41, 0),
    "order": {
        "id": uuid.uuid4(),
        "customer_id": uuid.uuid4(),
        "items": [
            {"product_id": uuid.uuid4(), "sku": "NVX-9000", "qty": 2},
        ],
    },
}

print(json.dumps(api_response, indent=2, cls=ApiEncoder))
# {
#   "request_id": "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e",
#   "created_at": "2026-03-14T09:41:00",
#   "order": {
#     "id": "a1c2e3f4-...",
#     ...
#   }
# }

Якщо ви будуєте FastAPI або Flask API і хочете послідовно повертати UUID у відповідях, рекомендується визначити підклас encoder один раз на рівні застосунку і зареєструвати його глобально, а не конвертувати UUID вручну в кожному обробнику. FastAPI (який використовує Pydantic під капотом) автоматично серіалізує поля uuid.UUID у рядки, тому там підклас encoder не потрібен. У Flask, де серіалізація є більш ручною, підклас JSONEncoder або параметр default=str є найпростішим рішенням.

Важливий нюанс стосується також журналювання. Популярні бібліотеки Python, такі як structlog або стандартний модуль logging, не серіалізують об'єкти uuid.UUID автоматично. Якщо передати об'єкт UUID безпосередньо у поле структурованого логу, деякі бекенди журналювання перетворять його через repr(), даючи рядок вигляду UUID('3b1f8a9d-...') замість очікуваного '3b1f8a9d-...'. Щоб уникнути цього, явно перетворюйте UUID перед передачею в поля логу: logger.info("Request received", request_id=str(uuid_obj)). Це робить значення безпосередньо придатними для пошуку у Kibana, Loki або CloudWatch Logs без додаткових перетворень у конвеєрі журналювання.

Для разової серіалізації параметр default= простіший за підклас:

Python 3.8+ — параметр default= (разовий виклик)
import json, uuid

event_id = uuid.uuid4()
payload = {"event_id": event_id, "action": "checkout"}

# Передаємо callable; викликається тільки для типів, які json не вміє обробляти
json_str = json.dumps(payload, default=str)
print(json_str)  # {"event_id": "3b1f8a9d-...", "action": "checkout"}

Отримуючи відповідь від зовнішнього API, розбирайте рядки UUID назад в об'єкти, щоб код мав повний набір атрибутів та типобезпеку:

Python 3.8+ — розбір UUID з відповіді API
import json
import uuid
import requests

def fetch_shipment(shipment_id: str) -> dict:
    """Fetch a shipment and return with typed UUID fields."""
    response = requests.get(
        f"https://api.logistics.example.com/v2/shipments/{shipment_id}",
        headers={"Accept": "application/json"},
        timeout=10,
    )
    response.raise_for_status()
    data = response.json()

    # Розбираємо поля UUID назад в об'єкти uuid.UUID
    try:
        data["id"] = uuid.UUID(data["id"])
        data["carrier_id"] = uuid.UUID(data["carrier_id"])
    except (KeyError, ValueError) as exc:
        raise RuntimeError(f"Malformed shipment response: {exc}") from exc

    return data

Щоб оновити поля UUID у JSON-файлі на диску — наприклад, ротувати ідентифікатор кореляції у файлі конфігурації або тестових даних — зчитайте, змініть і атомарно запишіть назад:

Python 3.8+ — читання, оновлення та запис JSON-файлу
import json, uuid

def rotate_correlation_id(path: str) -> str:
    """Replace or add 'correlation_id' in a JSON file. Returns the new UUID."""
    try:
        with open(path) as f:
            data = json.load(f)
    except FileNotFoundError:
        data = {}
    except json.JSONDecodeError as exc:
        raise ValueError(f"Invalid JSON in {path!r}: {exc}") from exc

    new_id = str(uuid.uuid4())
    data["correlation_id"] = new_id

    with open(path, "w") as f:
        json.dump(data, f, indent=2)

    return new_id

Ще один важливий аспект серіалізації — використання dataclasses.asdict(). Вбудована функція перетворює датаклас на словник рекурсивно, але не конвертує поля uuid.UUID на рядки — вони залишаються об'єктами UUID у словнику. Якщо після asdict() передати результат у json.dumps() без власного encoder, отримаєте TypeError. Рішення — передати default=str або власний клас encoder: json.dumps(dataclasses.asdict(obj), default=str). Pydantic v2 уникає цієї проблеми: метод model_dump_json() автоматично серіалізує UUID як рядки, а model_dump() повертає словник із необробленими об'єктами UUID — обирайте відповідно до наступного кроку в конвеєрі обробки даних.

Якщо ви не хочете запускати скрипт щоразу, щоб перевірити UUID з відповіді API, вставте його безпосередньо у UUID Decoder — він покаже версію, варіант та всі поля без жодного коду.

Генерація UUID v4 з командного рядка за допомогою Python

Модуль uuid у Python не надає окремої підкоманди CLI, як python -m json.tool, але однорядковий вираз вирішує ту саму задачу. Це зручно у shell-скриптах, CI-конвеєрах та завжди, коли потрібен тимчасовий ідентифікатор без відкриття REPL.

bash
# Один UUID v4
python3 -c "import uuid; print(uuid.uuid4())"
# 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e

# Формат hex без дефісів — зручний для назв файлів та змінних середовища
python3 -c "import uuid; print(uuid.uuid4().hex)"
# 3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e

# Generate 5 UUIDs for a batch seed script
python3 -c "import uuid; [print(uuid.uuid4()) for _ in range(5)]"

# Use in a shell variable
DEPLOY_ID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "Deploying with ID: $DEPLOY_ID"
Примітка:На macOS та більшості дистрибутивів Linux є утиліта uuidgen (на C), яка генерує UUID v4 і працює швидше для чистих shell-скриптів. Використовуйте однорядковий вираз Python, коли вже перебуваєте у Python-оточенні та хочете узгодженості з тим, як UUID генеруються у коді застосунку.

У контексті CI/CD командний рядок Python також корисний для генерації унікального ідентифікатора розгортання або версії збірки. Більшість CI-систем (GitHub Actions, GitLab CI, Jenkins) надають змінні середовища на зразок $GITHUB_RUN_ID, але якщо потрібен глобально унікальний ідентифікатор поза межами однієї платформи CI, UUID v4 через Python є надійним рішенням. Наприклад, при одночасних розгортаннях на кілька регіонів кожному екземпляру можна призначити власний DEPLOYMENT_ID без ризику конфліктів між регіонами — оскільки UUID v4 генерується незалежно без центральної координації.

Якщо у вашому проекті вже є файл Makefile, зручно додати ціль для генерації UUID прямо через make:

Makefile
# Makefile — зручна ціль для генерації UUID
uuid:
	@python3 -c "import uuid; print(uuid.uuid4())"

# Використання: make uuid

Такий підхід дозволяє будь-якому члену команди отримати свіжий UUID без знання синтаксису Python — достатньо запустити make uuid у корені проекту. Особливо зручно при написанні міграцій бази даних вручну, де кожному міграційному файлу або seed-запису потрібен унікальний ідентифікатор. Ту саму техніку можна застосувати в скриптах ініціалізації Docker-контейнерів або Terraform-провіжнерах, де потрібно задати UUID-ідентифікатор ресурсу ще до першого розгортання. Оскільки Python присутній у більшості Linux-образів за замовчуванням, цей однорядковий вираз є портативним рішенням без будь-яких зовнішніх залежностей.

Високопродуктивна генерація UUID v4 за допомогою uuid-utils

Стандартна бібліотечна uuid.uuid4() достатньо швидка для більшості застосунків — кілька мікросекунд на виклик дозволяє обробляти тисячі ідентифікаторів на секунду без проблем. Якщо ж UUID генеруються на гарячому шляху сервісу з високою пропускною здатністю (масові вставки, телеметрія на рівні подій у масштабі або генерація ідентифікаторів запитів під великим навантаженням), uuid-utils є повним замінником на базі Rust, що за бенчмарками приблизно у 10 разів швидший за стандартну бібліотеку.

bash — install
pip install uuid-utils
Python 3.8+ — uuid-utils як повний замінник
# uuid_utils is a drop-in replacement for the stdlib uuid module
import uuid_utils as uuid

# Same API as stdlib
request_id = uuid.uuid4()
print(request_id)           # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e
print(str(request_id))      # canonical string
print(request_id.hex)       # no-dashes hex
print(request_id.version)   # 4

# Also supports v7 (time-ordered, great for DB primary keys)
time_ordered_id = uuid.uuid7()
print(time_ordered_id)      # starts with current-timestamp prefix
Попередження:Стандартний режим uuid-utils повертає власний тип об'єкта UUID, який сумісний зі стандартною бібліотекою у більшості випадків. Якщо вам потрібні суворі перевірки isinstance(u, uuid.UUID) зі стандартної бібліотеки, використовуйте режим сумісності: import uuid_utils.compat as uuid. Режим сумісності дещо повільніший за стандартний, але все одно швидший за стандартну бібліотеку.

Якщо ви хочете порівняти продуктивність на власній машині, запустіть простий мікробенчмарк за допомогою вбудованого модуля timeit:

bash — порівняння продуктивності
python3 -c "
import timeit, uuid
stdlib_time = timeit.timeit('uuid.uuid4()', setup='import uuid', number=100_000)
print(f'stdlib uuid4: {stdlib_time:.3f}s for 100k calls')
"

# Потім порівняйте з uuid-utils (потребує pip install uuid-utils)
python3 -c "
import timeit
uutils_time = timeit.timeit('uuid.uuid4()', setup='import uuid_utils as uuid', number=100_000)
print(f'uuid-utils uuid4: {uutils_time:.3f}s for 100k calls')
"

На більшості машин стандартна бібліотека генерує 100 000 UUID v4 приблизно за 0.1–0.2 секунди, тоді як uuid-utils виконує ту саму операцію за 0.01–0.02 секунди. Для більшості веб-застосунків різниця незначна — HTTP-накладні витрати на порядки більші, ніж час генерації UUID. Різниця стає відчутною тільки у сценаріях пакетної обробки або потокової передачі даних, де UUID генеруються для сотень тисяч подій на секунду в одному процесі.

UUID v4 у Dataclass та моделях Pydantic

Датакласи Python та моделі Pydantic обидва нативно підтримують поля UUID. Ключовий шаблон при використанні UUID як автоматично згенерованого значення за замовчуванням — передавати посилання на функцію, а не результат її виклику — інакше всі екземпляри отримають один і той самий UUID.

Python 3.10+ — датаклас із UUID за замовчуванням
from dataclasses import dataclass, field
import uuid

@dataclass
class WorkerJob:
    job_id: uuid.UUID = field(default_factory=uuid.uuid4)
    worker_id: str = "worker-01"
    payload: dict = field(default_factory=dict)

job1 = WorkerJob(payload={"task": "resize_image", "src": "uploads/img_4932.png"})
job2 = WorkerJob(payload={"task": "send_email",   "to":  "ops@example.com"})

print(job1.job_id)  # unique per instance
print(job2.job_id)  # different from job1.job_id
print(job1.job_id == job2.job_id)  # False
Python 3.10+ — модель Pydantic v2 із UUID
from pydantic import BaseModel, Field
import uuid

class OrderEvent(BaseModel):
    event_id: uuid.UUID = Field(default_factory=uuid.uuid4)
    order_id: uuid.UUID
    status: str
    amount_cents: int

event = OrderEvent(
    order_id=uuid.UUID("a1c2e3f4-5b6d-4e7f-8c9d-0a1b2c3d4e5f"),
    status="payment_confirmed",
    amount_cents=4995,
)

print(event.model_dump_json(indent=2))
# {
#   "event_id": "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e",
#   "order_id": "a1c2e3f4-5b6d-4e7f-8c9d-0a1b2c3d4e5f",
#   "status": "payment_confirmed",
#   "amount_cents": 4995
# }
# Pydantic v2 serializes uuid.UUID as string automatically

UUID v4 у SQLAlchemy

SQLAlchemy надає вбудований тип стовпця UUID (або застарілий псевдонім Uuid у SQLAlchemy 2.0+), який прозоро обробляє серіалізацію та десеріалізацію залежно від діалекту бази даних. Для PostgreSQL він використовує рідний тип uuid; для SQLite та MySQL він зберігає значення як TEXT або CHAR(36).

Python 3.10+ — SQLAlchemy 2.0 ORM з UUID
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
import uuid

class Base(DeclarativeBase):
    pass

class Product(Base):
    __tablename__ = "products"

    id: Mapped[uuid.UUID] = mapped_column(
        PG_UUID(as_uuid=True),
        primary_key=True,
        default=uuid.uuid4,   # фабрика, не результат виклику
    )
    sku: Mapped[str] = mapped_column(String(64), unique=True)
    price_cents: Mapped[int]

# При вставці нового продукту SQLAlchemy автоматично
# викличе uuid.uuid4() для заповнення поля id
new_product = Product(sku="NVX-9000", price_cents=4995)
# new_product.id буде об'єктом uuid.UUID після flush/commit

Якщо ви використовуєте SQLAlchemy Core замість ORM, передавайте uuid.uuid4() безпосередньо у словник значень рядка — драйвер адаптує тип автоматично для PostgreSQL. Для SQLite і MySQL з текстовим зберіганням викликайте str(uuid.uuid4()) самостійно. Зверніть увагу, що при зчитуванні рядків із SQLite через SQLAlchemy поля UUID повертаються як рядки — після читання перетворіть їх назад на об'єкти uuid.UUID, якщо подальший код покладається на атрибути об'єкта.

Типові помилки при генерації UUID v4 у Python

Усі чотири шаблони зустрічаються на код-рев'ю та у виробничих інцидентах — їх легко пропустити, бо вони не викидають помилку одразу.

Виклик uuid4 без дужок як значення за замовчуванням

Проблема: Передача uuid.uuid4 (об'єкта функції) як значення за замовчуванням у датаклас або модель без обгортання у default_factory — Python обчислює значення за замовчуванням один раз під час визначення класу, тому всі екземпляри матимуть один UUID.

Рішення: Використовуйте default_factory=uuid.uuid4 у датакласах або Field(default_factory=uuid.uuid4) у Pydantic, щоб новий UUID генерувався для кожного екземпляра.

Before · Python
After · Python
@dataclass
class Session:
    # НЕПРАВИЛЬНО: обчислюється один раз, всі екземпляри матимуть цей UUID
    session_id: uuid.UUID = uuid.uuid4()
@dataclass
class Session:
    # ПРАВИЛЬНО: фабрика викликається для кожного екземпляра
    session_id: uuid.UUID = field(default_factory=uuid.uuid4)
Порівняння об'єкта UUID з простим рядком

Проблема: Об'єкти uuid.UUID не рівні простим рядкам, тому session_id == '3b1f8a9d-...' завжди повертає False, навіть коли значення збігається — непомітно ламаючи пошуки.

Рішення: Завжди порівнюйте UUID з UUID: огортайте рядок у uuid.UUID() перед порівнянням або перетворюйте обидві сторони на str().

Before · Python
After · Python
# Повертає False, навіть коли значення збігаються
if record["session_id"] == "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e":
    revoke_session(record)
target = uuid.UUID("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e")
if record["session_id"] == target:   # both are uuid.UUID
    revoke_session(record)

# Або нормалізуйте все до рядків на межі:
if str(record["session_id"]) == str(target):
    revoke_session(record)
Зберігання .hex замість str() і втрата дефісів

Проблема: uuid_obj.hex повертає 32-символьний рядок без дефісів. Якщо нижчестоящий код очікує канонічний 36-символьний формат із дефісами (більшість API та баз даних — так), він відхилить або непомітно неправильно розбере значення.

Рішення: Використовуйте str(uuid_obj) для канонічного 36-символьного формату, якщо тільки у вас немає явної вимоги до компактного hex-формату.

Before · Python
After · Python
# Зберігає "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" — без дефісів
payload = {"correlation_id": request_id.hex}
# Зберігає "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" — стандартний формат
payload = {"correlation_id": str(request_id)}
Використання random.random() або secrets.token_hex() замість UUID

Проблема: random.random() не є криптографічно безпечним, а secrets.token_hex(16) повертає 32-символьний hex-рядок, який не є дійсним UUID — нижчестоящі валідатори, що викликають uuid.UUID() на ньому, викинуть ValueError.

Рішення: Використовуйте uuid.uuid4() завжди, коли приймаюча система очікує ідентифікатор у форматі UUID. Використовуйте secrets.token_hex() лише тоді, коли явно потрібен випадковий токен не у форматі UUID.

Before · Python
After · Python
import random, secrets

# Не є UUID — не пройде валідацію uuid.UUID()
request_id = secrets.token_hex(16)   # "a1b2c3d4e5f6..."
session_id = str(random.random())    # "0.8273..." — not even close
import uuid

request_id = str(uuid.uuid4())  # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"
# Valid UUID v4, cryptographically secure

Методи генерації UUID у Python — швидке порівняння

Усі наведені методи генерують 128-бітні ідентифікатори, але відрізняються джерелом ентропії, характеристиками конфіденційності та потребою у сторонніх пакетах.

Метод
Джерело
Унікальність
Конфіденційність
Власні типи
Швидкість
Встановлення
uuid.uuid4()
Випадкове (os.urandom)
2¹²² випадкових біт
Н/Д
Стандартна
Вбудовано
uuid.uuid1()
Мітка часу + MAC
Час + хост
❌ (MAC розкрито)
Н/Д
Стандартна
Вбудовано
uuid.uuid3(name)
Хеш MD5
Детермінований
namespace+name
Стандартна
Вбудовано
uuid.uuid5(name)
Хеш SHA-1
Детермінований
namespace+name
Стандартна
Вбудовано
uuid_utils.uuid4()
Rust os.urandom
2¹²² випадкових біт
Н/Д
~10× швидше
pip install uuid-utils
secrets.token_hex(16)
os.urandom
128 випадкових біт
Н/Д
Швидка
Вбудовано
str(uuid.uuid4())
Випадкове
2¹²² випадкових біт
Н/Д
Стандартна
Вбудовано

Використовуйте uuid.uuid4() для універсальних унікальних ідентифікаторів у веб-застосунках, розподілених системах та первинних ключах бази даних, коли сортованість не є вимогою. Використовуйте uuid.uuid5() (або v3) для детермінованих ідентифікаторів, похідних від відомого простору імен та назви — наприклад, для генерації стабільного ідентифікатора канонічного URL. Переходьте на uuid_utils.uuid7() коли потрібні впорядковані за часом ідентифікатори для індексів бази даних (уникає розщеплення сторінок у B-tree індексах при високій частоті вставок). Використовуйте uuid_utils.uuid4() коли вузьким місцем є швидкість самої генерації.

На практиці слід розрізняти два сценарії використання. Перший — ідентифікатори, що передаються між сервісами або зберігаються у базі даних тривалий час: тут важлива глобальна унікальність і стандартна читабельність формату, тому uuid.uuid4() є правильним вибором. Другий — короткоживучі токени для одноразових дій (скидання паролю, підтвердження email): тут secrets.token_urlsafe(32) може бути доречнішим, оскільки він дає більше ентропії у коротшому рядку без прив'язки до формату UUID. Обидва є криптографічно безпечними; відмінність — лише у форматі та довжині.

Вибір між методами часто залежить від контексту зберігання. Якщо ваш стек повністю керований (наприклад, Supabase або PlanetScale), де тип стовпця uuid підтримується нативно, передавайте об'єкт uuid.UUID напряму — драйвер або ORM обробить перетворення. Якщо ваш стек змішаний (наприклад, Python + Redis + MongoDB), узгодьте єдиний строковий формат між всіма компонентами — зазвичай це канонічний формат із дефісами — та нормалізуйте на межах введення/виведення. Непослідовність (частина коду зберігає рядок із дефісами, частина — без) є однією з найпоширеніших причин помилок при переміщенні ідентифікаторів між сервісами.

Ще один важливий аспект вибору представлення — передача UUID між різними мовами програмування. Якщо ваш Python-бекенд обмінюється ідентифікаторами з JavaScript-фронтендом або Go-мікросервісом, завжди використовуйте канонічний рядковий формат із дефісами: str(uuid_obj). Він однозначно розпізнається стандартними UUID-парсерами в усіх основних мовах — Java, Go, Rust, JavaScript та інших. Уникайте передачі UUID у форматі .int або .bytes_le через HTTP-межі — інтерпретація байтового порядку може відрізнятися між реалізаціями, а числовий формат не є самодокументованим і вимагає явної домовленості між командами.

UUID v4 проти UUID v7 — що обрати?

Найпоширеніше практичне питання — чи використовувати UUID v4 або новіший UUID v7 для первинних ключів бази даних. Коротка відповідь: використовуйте UUID v4 за замовчуванням; переходьте на UUID v7 лише тоді, коли фрагментація індексу є виміряною проблемою.

Значення UUID v4 є повністю випадковими, що означає: вставки потрапляють у випадкові позиції B-tree індексу. При помірній частоті вставок (сотні — низькі тисячі на секунду) це нормально — індекс поміщається у буферний пул, а випадкові записи дешеві. При дуже високій частоті вставок випадкове розміщення спричиняє часте розщеплення сторінок та промахи кешу, збільшуючи підсилення запису і уповільнюючи запити.

UUID v7 вбудовує Unix-мітку часу з точністю до мілісекунди у найзначущі біти, тому рядки, вставлені близько за часом, також розташовуються поряд в індексі. Це надає B-tree індексам (PostgreSQL, MySQL, SQLite) поведінку, близьку до автоінкрементного цілого числа: нові рядки завжди додаються в кінець індексу, усуваючи розщеплення сторінок. Компроміс у тому, що UUID v7 кодує мітку часу, яка розкриває час створення — уникайте його для ідентифікаторів, видимих користувачам, де час створення є конфіденційним.

У Python UUID v7 ще не входить до стандартної бібліотеки (станом на Python 3.12). Генеруйте його за допомогою pip install uuid-utils та виклику uuid_utils.uuid7(). Він повертає об'єкт із тим самим набором атрибутів, що й uuid.UUID, тому міграція з v4 — це зміна одного рядка у фабриці ідентифікаторів.

Практичне правило: починайте з UUID v4 і залишайтесь на ньому, поки профілювання не вкаже на реальну проблему з індексом. Переважна більшість застосунків ніколи не досягає порогу навантаження, при якому фрагментація B-tree стає вимірюваною проблемою — HTTP-накладні витрати і затримка бази даних на порядки більші, ніж розщеплення сторінок індексу. Якщо ж ви вже встановили uuid-utils заради швидкості генерації, перехід на uuid_utils.uuid7() для первинних ключів нічого не коштує і може запобігти проблемам зі зростанням у майбутньому. Головне обмеження — не використовувати UUID v7 для ідентифікаторів, видимих зовнішнім користувачам, де час створення є конфіденційним, оскільки перші 48 біт містять Unix-мітку часу, яку можна прочитати без жодних інструментів.

З точки зору сумісності версій Python, усі підходи, описані у цій статті, підтримуються починаючи з Python 3.8. Модуль uuid присутній у стандартній бібліотеці з Python 2.5, тому uuid.uuid4() є найбільш портативним рішенням між будь-якими версіями Python, які ви підтримуєте. Синтаксис датакласів та типові анотації, показані у прикладах ORM, вимагають Python 3.7+ та Python 3.9+ відповідно для вбудованих дженериків (наприклад, list[uuid.UUID] замість List[uuid.UUID] з модуля typing). Якщо ви підтримуєте Python 3.8, додайте from __future__ import annotations на початку файлів із типовими анотаціями, щоб відкласти їх обчислення — це дозволяє використовувати сучасний синтаксис анотацій навіть на старих версіях інтерпретатора. Сам модуль uuid залишається стабільним API між усіма версіями.

Для альтернативи одним кліком без будь-якого налаштування Python вставте рядок UUID у генератор та валідатор UUID v4 — він генерує, валідує та декодує всі поля прямо у браузері.

Часті запитання

Як згенерувати UUID v4 у Python?

Викличте uuid.uuid4() з вбудованого модуля uuid. Функція повертає об'єкт UUID — перетворіть його на рядок за допомогою str(), коли потрібне текстове представлення. Модуль входить до стандартної бібліотеки, тому встановлення через pip не потрібне.

Python
import uuid

session_id = uuid.uuid4()
print(session_id)           # e.g. 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e
print(str(session_id))      # same canonical string
print(session_id.hex)       # 3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e (no dashes)

У чому різниця між uuid.uuid4() і str(uuid.uuid4())?

uuid.uuid4() повертає об'єкт UUID з атрибутами .hex, .bytes, .int та .version. str(uuid.uuid4()) одразу перетворює цей об'єкт на 36-символьний канонічний рядок, відкидаючи сам об'єкт. Зберігайте об'єкт, якщо потрібні різні представлення; перетворюйте на рядок на межі передачі значення в JSON-відповідь, базу даних або HTTP-заголовок.

Python
import uuid

u = uuid.uuid4()
print(type(u))          # <class 'uuid.UUID'>
print(u.version)        # 4
print(u.hex)            # 32-char hex, no dashes
print(u.bytes)          # 16-byte binary
print(str(u))           # canonical 36-char string with dashes

Чи є uuid.uuid4() криптографічно безпечним?

Так. uuid.uuid4() у Python використовує os.urandom() всередині, який зчитує дані з криптографічно безпечного генератора випадкових чисел операційної системи (/dev/urandom у Linux/macOS, CryptGenRandom у Windows). 122 випадкових біти роблять імовірність колізій нехтовно малою для будь-якого реалістичного навантаження. Не плутайте його з random.random(), який не є криптографічно безпечним.

Python
import uuid, os

# uuid4 всередині викликає os.urandom(16)
raw = os.urandom(16)
# uuid4 встановлює біти версії та варіанту перед поверненням
u = uuid.UUID(bytes=raw, version=4)
print(u)  # valid v4 UUID from raw random bytes

Як перевірити, чи рядок є дійсним UUID v4 у Python?

Розберіть рядок за допомогою uuid.UUID() і перевірте атрибут .version. Якщо рядок не є дійсним UUID, uuid.UUID() викине ValueError — перехопіть його, щоб обробити некоректне введення. Це також перевірить правильність формату (дефіси, довжину).

Python
import uuid

def is_valid_uuid4(value: str) -> bool:
    try:
        u = uuid.UUID(value)
        return u.version == 4
    except ValueError:
        return False

print(is_valid_uuid4("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"))  # True
print(is_valid_uuid4("not-a-uuid"))                              # False
print(is_valid_uuid4("3b1f8a9d-2c4e-1f6a-8b0d-5e7c9f1a3d2e"))  # False (v1, not v4)

Як зберігати UUID у базі даних PostgreSQL або SQLite з Python?

Для PostgreSQL (через psycopg2 або asyncpg) передавайте об'єкт UUID напряму — драйвер адаптує його до рідного типу UUID. Для SQLite, який не має рідного типу UUID, зберігайте як TEXT за допомогою str(uuid_obj) або як BLOB за допомогою uuid_obj.bytes. SQLAlchemy має тип стовпця UUID, який автоматично обробляє це для різних діалектів.

Python
import uuid
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE events (id TEXT PRIMARY KEY, name TEXT)")

event_id = uuid.uuid4()
conn.execute("INSERT INTO events VALUES (?, ?)", (str(event_id), "user_signup"))
conn.commit()

row = conn.execute("SELECT * FROM events").fetchone()
# Reconstruct UUID object from stored string
retrieved_id = uuid.UUID(row[0])
print(retrieved_id.version)  # 4

Чи можна генерувати кілька UUID одночасно у Python?

Так — використовуйте генератор списків або генераторний вираз. Кожен виклик uuid.uuid4() є незалежним і гарантовано повертає унікальне значення. Для масової генерації, де важлива пропускна здатність, uuid-utils (на базі Rust) приблизно у 10 разів швидший за стандартну бібліотеку.

Python
import uuid

# Generate 5 unique trace IDs for a batch request
trace_ids = [str(uuid.uuid4()) for _ in range(5)]
for tid in trace_ids:
    print(tid)
# Each line is a distinct UUID v4

Пов'язані інструменти

  • UUID v4 GeneratorГенеруйте значення UUID v4 миттєво прямо у браузері — без необхідності налаштовувати Python. Скопіюйте одне значення або згенеруйте сотні одразу.
  • UUID v7 GeneratorГенеруйте впорядковані за часом значення UUID v7 — сортуються за часом створення, ідеально підходять як первинні ключі бази даних там, де важлива фрагментація індексу.
  • UUID DecoderАналізуйте будь-який UUID — версію, варіант, мітку часу (v1/v7) та поля вузла — без написання власного парсера.
  • JWT DecoderДекодуйте та аналізуйте JWT-токени, які часто містять UUID у полях subject (sub) або jti поряд із UUID сесій.
MS
Maria SantosBackend Developer

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.

DV
Dmitri VolkovТехнічний рецензент

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.