# dict: ключ и значение<no value>

## Что мы изучим?

**dict** (Dictionary, словарь) — структура, которая хранит **пары из ключа и значения**. Вместо доступа по позиции (`[0]`) обращаются по **имени** (`["name"]`).

## Зачем это нужно?

В tuple `("Dana", 24, "Haifa")` нужно **помнить**, что позиция 1 — это возраст. При 15 полях это рецепт для багов. dict решает эту проблему:

```python
student = {"name": "Dana", "age": 24, "city": "Haifa"}
student["age"]   # clear and readable
```

dict — пожалуй, **самая важная** структура на ближайшие недели:

- **JSON** (завтра): формат, в котором сохраняют файлы и пересылают информацию по сети. В Python он превращается в dict.
- **Темы бэкенда и баз данных**: каждый запрос и ответ в API — по сути dict. `{"id": 7, "name": "Dana"}`.
- Любую «запись» — пользователя, товар или заказ — удобно представлять как dict.

## Объяснение с основ

### Что такое dict?

- Каждый элемент — **пара**: **ключ** (Key) → **значение** (Value).
- **Уникальные ключи** — двух одинаковых ключей нет. Присваивание существующему ключу **заменяет** значение.
- **Изменяемый** (mutable).
- **Сохраняет порядок вставки** (начиная с Python 3.7).
- Ключ обычно `str` или `int`. Значение может быть **любым**: число, текст, список и даже другой dict.

{{< box "analogy" "Аналогия: словарь / телефонная книга" >}}
В настоящем словаре вы ищете **слово** (ключ) и получаете **определение** (значение). Вы не ищете «определение на позиции 5000 в книге». В телефонной книге: имя → номер. Два человека не могут появиться под абсолютно одинаковым именем как отдельные записи. Новая запись с тем же именем заменяет предыдущую.
{{< /box >}}

## Синтаксис

```python
# create
empty = {}
student = {"name": "Dana", "age": 24}

# read
student["name"]                 # "Dana" - KeyError if the key does not exist
student.get("email")            # None - no error if missing
student.get("email", "N/A")     # "N/A" - default value if missing

# add / update (same syntax!)
student["email"] = "dana@mail.com"   # new key -> added
student["age"] = 25                  # existing key -> updated

# delete
del student["email"]            # delete a key (KeyError if missing)
age = student.pop("age")        # delete and return the value

# check
"name" in student               # True - checks KEYS, not values
len(student)                    # number of key-value pairs

# iterate
for key in student:             # keys
for value in student.values():  # values
for key, value in student.items():   # pairs (tuples!)
```

## Первый пример кода

`week-2/student_dict.py`:

```python
student = {"name": "Dana", "age": 24, "city": "Haifa"}

print("Name:", student["name"])
print("Age:", student["age"])

student["age"] = 25
student["email"] = "dana@mail.com"
print("Updated:", student)

del student["city"]
print("After delete:", student)

print("Has email?", "email" in student)
print("Phone:", student.get("phone", "not provided"))
```

**Вывод:**

```text
Name: Dana
Age: 24
Updated: {'name': 'Dana', 'age': 25, 'city': 'Haifa', 'email': 'dana@mail.com'}
After delete: {'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
Has email? True
Phone: not provided
```

## Разбор кода построчно

| Строка | Объяснение |
|---|---|
| `{"name": "Dana", ...}` | Ключ, двоеточие, значение. Пары разделяются запятыми. |
| `student["name"]` | Чтение по ключу: значение `"Dana"`. |
| `student["age"] = 25` | Ключ **существует**, поэтому значение **обновляется**. |
| `student["email"] = "..."` | Ключа **нет**, поэтому он **добавляется** (в конец). Абсолютно тот же синтаксис! |
| `del student["city"]` | Удаление всей пары. |
| `"email" in student` | Проверяет, существует ли **ключ**. |
| `student.get("phone", "not provided")` | Ключа нет, поэтому возвращается значение по умолчанию, без ошибки. |

## Ещё один пример

**Список словарей** — самая распространённая структура для «настоящих» данных (таблица: каждый dict — строка). А также **подсчёт** с помощью dict. `week-2/products.py`:

```python
products = [
    {"id": 1, "name": "Laptop", "price": 3500, "category": "electronics"},
    {"id": 2, "name": "Mouse", "price": 80, "category": "electronics"},
    {"id": 3, "name": "Desk", "price": 900, "category": "furniture"},
    {"id": 4, "name": "Chair", "price": 450, "category": "furniture"},
]

print("--- Catalog ---")
for product in products:
    print(product["id"], product["name"], product["price"], "NIS")

expensive = []
for product in products:
    if product["price"] > 500:
        expensive.append(product["name"])
print("Over 500 NIS:", expensive)

count_by_category = {}
for product in products:
    category = product["category"]
    count_by_category[category] = count_by_category.get(category, 0) + 1
print("Count by category:", count_by_category)

for category, count in count_by_category.items():
    print(category, "->", count, "products")
```

**Вывод:**

```text
--- Catalog ---
1 Laptop 3500 NIS
2 Mouse 80 NIS
3 Desk 900 NIS
4 Chair 450 NIS
Over 500 NIS: ['Laptop', 'Desk']
Count by category: {'electronics': 2, 'furniture': 2}
electronics -> 2 products
furniture -> 2 products
```

**Разбор:**

- `products` — **список**, каждый элемент которого — **dict**. `product["price"]` = цена текущего товара в цикле.
- **Шаблон подсчёта:** `count_by_category.get(category, 0) + 1`. Если категории ещё нет в словаре, `get` возвращает 0, а затем 0+1 = 1. Если она уже есть, вы получаете текущее количество и прибавляете 1. Этот шаблон встречается снова и снова.
- `.items()` — возвращает пары `(key, value)` в виде tuple, а `for category, count in` распаковывает их (распаковка из предыдущего урока).

### dict внутри dict

Значение само может быть dict:

```python
user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl 10"},
    "hobbies": ["chess", "running"],
}
print(user["address"]["city"])   # Haifa
print(user["hobbies"][0])        # chess
```

Обращаются «слой за слоем»: `user["address"]` возвращает dict, а у него берут `["city"]`. Это именно структура JSON.

## Типичная ошибка

**Ошибка 1: `KeyError` для несуществующего ключа.**

```python
student = {"name": "Dana"}
print(student["age"])
```

```text
KeyError: 'age'
```

Исправление: `student.get("age")` (возвращает `None`), `student.get("age", 0)` (значение по умолчанию) или сначала проверить через `if "age" in student:`.

**Ошибка 2: искать значение через `in`.**

```python
student = {"name": "Dana"}
print("Dana" in student)
```

```text
False
```

`in` для dict проверяет **ключи**. Чтобы проверить значения: `"Dana" in student.values()`.

**Ошибка 3: опечатка в ключе, которая создаёт новый ключ вместо обновления.**

```python
student = {"name": "Dana", "age": 24}
student["Age"] = 25
print(student)
```

```text
{'name': 'Dana', 'age': 24, 'Age': 25}
```

Никакой ошибки! Просто добавился новый ключ `"Age"`, а `"age"` остался 24. Ключи чувствительны к регистру. Это тихая Logical Error, поэтому держите имена ключей единообразными (`snake_case`).

**Ошибка 4: список в качестве ключа.**

```python
locations = {[32, 35]: "Haifa"}
```

```text
TypeError: unhashable type: 'list'
```

Ключ должен быть **неизменяемым** (Immutable). Если нужен ключ из нескольких значений, используйте tuple: `{(32, 35): "Haifa"}`. (Связь с неизменяемостью объясним в следующем уроке.)

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Практика

Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.

### Упражнение 1: Карточка студента

Создайте словарь `student` с именем `"Dana"`, возрастом 24 и городом `"Haifa"`. Затем:

1. Обновите возраст на 25.
2. Добавьте email `"dana@mail.com"`.
3. Удалите город.
4. Напечатайте словарь, а также телефон с помощью `get` со значением по умолчанию `"none"`.

**Ожидаемый вывод:**

```text
{'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
none
```

{{< solution >}}
```python
student = {"name": "Dana", "age": 24, "city": "Haifa"}

student["age"] = 25
student["email"] = "dana@mail.com"
del student["city"]

print(student)
print(student.get("phone", "none"))
```

```text
{'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
none
```

`student["phone"]` выбросил бы `KeyError`. `get` возвращает значение по умолчанию, когда ключа нет.
{{< /solution >}}

### Упражнение 2: Подсчёт слов

Напишите функцию `count_words(text)`, которая возвращает словарь: каждое слово (в нижнем регистре) → сколько раз оно встречается.

```python
counts = count_words("the cat and the dog and THE bird")
print(counts)
```

**Ожидаемый вывод:**

```text
{'the': 3, 'cat': 1, 'and': 2, 'dog': 1, 'bird': 1}
```

{{< solution >}}
```python
def count_words(text):
    counts = {}
    for word in text.lower().split():
        counts[word] = counts.get(word, 0) + 1
    return counts


counts = count_words("the cat and the dog and THE bird")
print(counts)
```

```text
{'the': 3, 'cat': 1, 'and': 2, 'dog': 1, 'bird': 1}
```

`counts.get(word, 0)` возвращает 0 для слова, которое ещё не посчитано, — так не нужен отдельный `if` для нового слова.
{{< /solution >}}

### Упражнение 3: Список словарей

```python
products = [
    {"name": "Laptop", "price": 3500, "category": "electronics"},
    {"name": "Mouse", "price": 80, "category": "electronics"},
    {"name": "Desk", "price": 900, "category": "furniture"},
]
```

1. Напечатайте названия товаров, цена которых ниже 1000.
2. Постройте словарь, который считает, сколько товаров в каждой категории, и напечатайте его.

**Ожидаемый вывод:**

```text
Mouse
Desk
{'electronics': 2, 'furniture': 1}
```

{{< solution >}}
```python
products = [
    {"name": "Laptop", "price": 3500, "category": "electronics"},
    {"name": "Mouse", "price": 80, "category": "electronics"},
    {"name": "Desk", "price": 900, "category": "furniture"},
]

for product in products:
    if product["price"] < 1000:
        print(product["name"])

counts = {}
for product in products:
    category = product["category"]
    counts[category] = counts.get(category, 0) + 1
print(counts)
```

```text
Mouse
Desk
{'electronics': 2, 'furniture': 1}
```
{{< /solution >}}

### Упражнение 4: Перебор словаря

```python
stock = {"apple": 12, "banana": 0, "cherry": 5}
```

Переберите словарь через `items()` и напечатайте каждый товар и его количество. Для товара с количеством 0 напечатайте `out of stock`.

**Ожидаемый вывод:**

```text
apple -> 12
banana -> out of stock
cherry -> 5
```

{{< solution >}}
```python
stock = {"apple": 12, "banana": 0, "cherry": 5}

for name, quantity in stock.items():
    if quantity == 0:
        print(name, "->", "out of stock")
    else:
        print(name, "->", quantity)
```

```text
apple -> 12
banana -> out of stock
cherry -> 5
```

`items()` возвращает пары из ключа и значения, а распаковка в цикле разделяет каждую пару на две переменные.
{{< /solution >}}

### Упражнение 5: Вложенный словарь

```python
user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl"},
    "hobbies": ["chess", "running"],
}
```

Напечатайте город, первое хобби и добавьте хобби `"music"`. В конце напечатайте список хобби.

**Ожидаемый вывод:**

```text
Haifa
chess
['chess', 'running', 'music']
```

{{< solution >}}
```python
user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl"},
    "hobbies": ["chess", "running"],
}

print(user["address"]["city"])
print(user["hobbies"][0])
user["hobbies"].append("music")
print(user["hobbies"])
```

```text
Haifa
chess
['chess', 'running', 'music']
```

Каждые `[ ]` уходят на уровень глубже: `user["address"]` — словарь, а `["city"]` достаёт из него значение. Это именно структура JSON.
{{< /solution >}}

<!-- exercises:end -->

## Итоги

- **dict** `{key: value}` — доступ по **ключу**, а не по позиции. Ключи уникальны.
- `d[key] = value` — **добавляет**, если ключ новый, **обновляет**, если он существует.
- `d[key]` выбрасывает `KeyError` для отсутствующего ключа. `d.get(key, default)` не выбрасывает.
- `in` проверяет **ключи**. Перебор: `keys()`, `values()`, `items()` (пары).
- **Список словарей** = таблица. dict внутри dict = JSON.
- Шаблон подсчёта: `counts[x] = counts.get(x, 0) + 1`.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Официальная документация

- [Python Tutorial — Dictionaries](https://docs.python.org/3/tutorial/datastructures.html#dictionaries)
- [Built-in Types — Mapping Types (dict)](https://docs.python.org/3/builtins/stdtypes.html#mapping-types-dict-frozendict)

<!-- docs:end -->
