Генерація UUID v4 у Python — uuid.uuid4()
Використовуйте безкоштовний 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, якщо упорядкованість індексу є важливою вимогою.
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-заголовки.
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 до кожного виклику:
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 — наприклад, попереднього заповнення пакету рядків бази даних — генератор списків є ідіоматичним і читабельним підходом:
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 ефективніше використовувати генератор замість списку — він не завантажує всі значення одночасно в пам'ять:
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-бітного значення. Вибір правильного представлення для вашого рівня зберігання запобігає непомітному пошкодженню даних та марній витраті байтів.
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.
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() по всьому коду.
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= простіший за підклас:
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 назад в об'єкти, щоб код мав повний набір атрибутів та типобезпеку:
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-файлі на диску — наприклад, ротувати ідентифікатор кореляції у файлі конфігурації або тестових даних — зчитайте, змініть і атомарно запишіть назад:
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.
# Один 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"
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 — зручна ціль для генерації 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 разів швидший за стандартну бібліотеку.
pip install 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
isinstance(u, uuid.UUID) зі стандартної бібліотеки, використовуйте режим сумісності: import uuid_utils.compat as uuid. Режим сумісності дещо повільніший за стандартний, але все одно швидший за стандартну бібліотеку.Якщо ви хочете порівняти продуктивність на власній машині, запустіть простий мікробенчмарк за допомогою вбудованого модуля timeit:
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.
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) # Falsefrom 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 automaticallyUUID v4 у SQLAlchemy
SQLAlchemy надає вбудований тип стовпця UUID (або застарілий псевдонім Uuid у SQLAlchemy 2.0+), який прозоро обробляє серіалізацію та десеріалізацію залежно від діалекту бази даних. Для PostgreSQL він використовує рідний тип uuid; для SQLite та MySQL він зберігає значення як TEXT або CHAR(36).
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
Усі чотири шаблони зустрічаються на код-рев'ю та у виробничих інцидентах — їх легко пропустити, бо вони не викидають помилку одразу.
Проблема: Передача uuid.uuid4 (об'єкта функції) як значення за замовчуванням у датаклас або модель без обгортання у default_factory — Python обчислює значення за замовчуванням один раз під час визначення класу, тому всі екземпляри матимуть один UUID.
Рішення: Використовуйте default_factory=uuid.uuid4 у датакласах або Field(default_factory=uuid.uuid4) у Pydantic, щоб новий UUID генерувався для кожного екземпляра.
@dataclass
class Session:
# НЕПРАВИЛЬНО: обчислюється один раз, всі екземпляри матимуть цей UUID
session_id: uuid.UUID = uuid.uuid4()@dataclass
class Session:
# ПРАВИЛЬНО: фабрика викликається для кожного екземпляра
session_id: uuid.UUID = field(default_factory=uuid.uuid4)Проблема: Об'єкти uuid.UUID не рівні простим рядкам, тому session_id == '3b1f8a9d-...' завжди повертає False, навіть коли значення збігається — непомітно ламаючи пошуки.
Рішення: Завжди порівнюйте UUID з UUID: огортайте рядок у uuid.UUID() перед порівнянням або перетворюйте обидві сторони на str().
# Повертає 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)Проблема: uuid_obj.hex повертає 32-символьний рядок без дефісів. Якщо нижчестоящий код очікує канонічний 36-символьний формат із дефісами (більшість API та баз даних — так), він відхилить або непомітно неправильно розбере значення.
Рішення: Використовуйте str(uuid_obj) для канонічного 36-символьного формату, якщо тільки у вас немає явної вимоги до компактного hex-формату.
# Зберігає "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" — без дефісів
payload = {"correlation_id": request_id.hex}# Зберігає "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" — стандартний формат
payload = {"correlation_id": str(request_id)}Проблема: random.random() не є криптографічно безпечним, а secrets.token_hex(16) повертає 32-символьний hex-рядок, який не є дійсним UUID — нижчестоящі валідатори, що викликають uuid.UUID() на ньому, викинуть ValueError.
Рішення: Використовуйте uuid.uuid4() завжди, коли приймаюча система очікує ідентифікатор у форматі UUID. Використовуйте secrets.token_hex() лише тоді, коли явно потрібен випадковий токен не у форматі UUID.
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() для універсальних унікальних ідентифікаторів у веб-застосунках, розподілених системах та первинних ключах бази даних, коли сортованість не є вимогою. Використовуйте 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 не потрібне.
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-заголовок.
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(), який не є криптографічно безпечним.
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 — перехопіть його, щоб обробити некоректне введення. Це також перевірить правильність формату (дефіси, довжину).
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, який автоматично обробляє це для різних діалектів.
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 разів швидший за стандартну бібліотеку.
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 сесій.
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.
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.