Модуль 4 · Урок 14 із 58

Словники: ключі, безпечний доступ і schema

Dictionary пов’язує унікальний key із value. Це зручно, але не робить дані автоматично структурованими: треба визначити дозволені keys, required fields, типи values, duplicate policy і різницю між «поля немає» та «значення поля — None».

dictkeysgetschema

Безпечна практика модуля 4

Пакет містить тільки synthetic records, pure collection functions, import-safe CLI і локальні tests. Жодних персональних чи production-даних, secrets, мережевих викликів або зовнішніх залежностей.

Завантажити практичний пакет →

Mapping contract

Для record у dict запишіть schema до коду: required keys, optional keys, тип кожного value, unknown-key policy та normalization boundary. Dict із довільними keys — гнучкий лише до першої помилки в написанні, яка тихо створить нове поле.

case = {
    "id": "case-17",
    "route": "urgent",
    "minutes": 75,
    "tags": ["priority", "synthetic"],
}
Contract: id, route і minutes required; tags optional list[str]; unknown keys rejected at boundary.

Keys мають бути hashable й унікальні

String, number або tuple з hashable items може бути key. List чи dict не може, бо його equality-relevant state змінюється. Якщо записати те саме key вдруге, попереднє value буде замінено — це не duplicate history.

index = {"case-17": "review"}
index["case-17"] = "urgent"
print(index)  # {"case-17": "urgent"}

Тому під час побудови index duplicate IDs треба ловити до assignment, якщо overwrite не є свідомою policy.

[] і get відповідають на різні питання

# required: відсутність є помилкою schema
case_id = case["id"]

# optional: відсутність має documented default
tags = case.get("tags", [])

Subscript піднімає KeyError і добре сигналізує порушення required contract. get доречний для optional field. Але case.get("owner") не відрізняє missing від explicit None; коли різниця важлива, перевіряйте "owner" in case або використовуйте окремий sentinel.

Iteration має називати те, що читає

for key in case:
    print(key)

for key, value in case.items():
    print(key, value)

Iteration по dict за замовчуванням дає keys. keys(), values() і items() повертають dynamic views, а не frozen snapshots. Dict зберігає insertion order, але sorted business output треба створювати явно: for key in sorted(case).

Merge потребує collision policy

defaults = {"route": "standard", "tags": []}
incoming = {"route": "urgent", "id": "case-17"}
merged = {**defaults, **incoming}  # right side wins

Right-wins синтаксис короткий, але не відповідає, чи caller мав право змінити route. Для конфігів, permissions і identifiers collisions треба перевіряти й документувати. setdefault теж mutation; не ховайте його всередині read-only validation.

Вкладені dict не замінюють validation

Ланцюжок payload["user"]["profile"]["name"] має кілька окремих failure points і нічого не каже про types. Розбийте boundary validation на маленькі checks, поверніть нормалізований record і тільки потім виконуйте business logic.

Definition of Done
  • Required/optional/unknown fields визначені.
  • Duplicate keys та merge collisions мають policy.
  • Missing не змішується з explicit None.
  • Output order створюється свідомо.
  • Caller-owned nested values не мутуються під час validation.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію Python.

Практична перевірка · урок 14 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. Synthetic record має required field id. Який доступ краще одразу сигналізує порушення schema, якщо field відсутнє?
2. Optional tags можуть бути відсутні й тоді означають порожню collection. Який доступ відображає цей documented default?
3. Чому record.get('owner') не може сам відрізнити missing key від key із explicit value None?