Основы программирования
Объекты и словари
**Загадка:** У тебя есть данные пользователя: имя, возраст, email, город. В списке это `["Алиса", 25, "alice@mail.com", "Москва"]`. Что означает `user[2]`? А если добавить телефон - все индексы сломаются! Есть решение лучше.
Словарь (dict) хранит данные по ключам, а не по индексам. Вместо `user[2]` пишешь `user["email"]` - понятно и надёжно.
Цели урока
- Понять концепцию словаря (dict)
- Освоить создание, чтение, изменение
- Научиться итерировать по словарям
- Познакомиться с вложенными структурами
Предварительные знания
- Списки (урок 11)
- Циклы (урок 7)
- Строки (урок 5)
JSON - формат данных интернета - это словари! API возвращают словари. Конфиги - словари. Базы данных - записи-словари. Это самая важная структура данных.
- **API ответы**: {"status": "ok", "data": [...]}
- **Конфигурации**: {"debug": true, "port": 3000}
- **Профили**: {"name": "Алиса", "age": 25}
- **Кэширование**: {"url": "cached_response"}
От Sketchpad и Simula 67 до Smalltalk, Python everything-is-object и dataclasses
Идея объекта как сущности с данными и поведением появилась в 1963 году в Sketchpad Ивана Сазерленда (MIT, докторская в Lincoln Lab). Объекты там были графическими примитивами с прототипами. В 1967 году Кристен Нюгор и Уле-Йохан Даль в Норвежском вычислительном центре выпустили Simula 67, первый язык с классами, наследованием и виртуальными методами. От Simula напрямую произошёл C++ (Бьёрн Страуструп начал в 1979, релиз 1983 как C with Classes). В Xerox PARC Алан Кэй и команда (Дэн Ингаллс, Адель Голдберг) с 1972 по 1980 строили Smalltalk: всё было объектом, общение через сообщения, динамическая типизация. Smalltalk-80 дал миру MVC, рефакторинг и IDE. Java от Джеймса Гослинга (Sun, 1995) взяла модель классов Simula и виртуальную машину похожую на Smalltalk. В том же 1995 Брендан Эйх создал JavaScript за 10 дней, выбрал прототипное наследование вместо классов (классы как синтаксис добавили в ES6 2015). Python с 1991 года реализовал философию everything is an object: число, функция, класс, модуль это объекты. type(x) работает для любого значения. Словарь стал главной структурой данных, dict реализует и сам namespace модуля, и атрибуты экземпляра (__dict__). В Python 3.7 (2017, PEP 468) dict гарантирует порядок вставки, а PEP 557 (Eric V. Smith, Python 3.7, 2018) принёс dataclasses: декоратор @dataclass генерирует __init__, __repr__ и __eq__ без бойлерплейта.
Создание и основы
**Словарь (dict)** - коллекция пар "ключ: значение". Ключ - уникальный идентификатор, значение - любые данные.
Создание словарей
Разные способы
```python # Литерал - фигурные скобки user = { "name": "Алиса", "age": 25, "email": "alice@mail.com" } # Пустой словарь empty = {} empty2 = dict() # Из списка пар pairs = [("a", 1), ("b", 2)] d = dict(pairs) # {"a": 1, "b": 2} # Kwargs синтаксис (только строковые ключи) person = dict(name="Боб", age=30) ```
Ключи и значения
Что может быть ключом?
```python # Ключи - неизменяемые типы: str, int, tuple data = { "name": "Алиса", # Строка - ключ 42: "ответ", # Число - ключ (0, 0): "начало", # Кортеж - ключ } # НЕЛЬЗЯ: список как ключ (изменяемый) # bad = {[1, 2]: "value"} # TypeError! # Значения - любые типы complex_dict = { "list": [1, 2, 3], "nested": {"a": 1}, "func": print } ```
**Ключи уникальны!** Если добавить ключ, который уже существует - старое значение будет перезаписано.
Что выведет: len({"a": 1, "b": 2, "a": 3})?
Доступ и изменение
Доступ к значениям - через квадратные скобки с ключом или метод `.get()`.
Чтение значений
Два способа
```python user = {"name": "Алиса", "age": 25} # Способ 1: квадратные скобки print(user["name"]) # "Алиса" # print(user["email"]) # KeyError! Ключа нет # Способ 2: .get() - безопасный print(user.get("name")) # "Алиса" print(user.get("email")) # None (нет ошибки) print(user.get("email", "не указан")) # "не указан" (default) ```
Изменение и добавление
Словари изменяемы
```python user = {"name": "Алиса", "age": 25} # Изменение существующего user["age"] = 26 print(user) # {'name': 'Алиса', 'age': 26} # Добавление нового user["email"] = "alice@mail.com" print(user) # {'name': 'Алиса', 'age': 26, 'email': 'alice@mail.com'} # Удаление del user["age"] print(user) # {'name': 'Алиса', 'email': 'alice@mail.com'} # Проверка наличия ключа print("name" in user) # True print("age" in user) # False (удалили) ```
d[key] и d.get(key) - одно и то же
d[key] вызовет KeyError, если ключа нет. d.get(key) вернёт None.
Используй d[key], когда уверен, что ключ есть. Используй d.get(key, default) для безопасного доступа с fallback значением.
Что вернёт: {"a": 1}.get("b", 0)?
Методы словарей
Словари имеют полезные методы для работы с ключами, значениями и парами.
| Метод | Описание | Пример |
|---|---|---|
| .keys() | Все ключи | d.keys() → dict_keys(['a', 'b']) |
| .values() | Все значения | d.values() → dict_values([1, 2]) |
| .items() | Пары (ключ, значение) | d.items() → [('a', 1), ('b', 2)] |
| .pop(key) | Удалить и вернуть | d.pop('a') → 1 |
| .update(d2) | Объединить словари | d.update({'c': 3}) |
| .setdefault(k,v) | Получить или установить | d.setdefault('x', 0) |
| .clear() | Очистить | d.clear() → {} |
Методы в действии
Практические примеры
```python scores = {"Alice": 85, "Bob": 92, "Charlie": 78} # Получить все данные print(list(scores.keys())) # ['Alice', 'Bob', 'Charlie'] print(list(scores.values())) # [85, 92, 78] print(list(scores.items())) # [('Alice', 85), ('Bob', 92), ...] # Удаление с получением bob_score = scores.pop("Bob") print(bob_score) # 92 print(scores) # {'Alice': 85, 'Charlie': 78} # Объединение scores.update({"Diana": 95, "Alice": 88}) # Alice перезаписан print(scores) # {'Alice': 88, 'Charlie': 78, 'Diana': 95} # setdefault - если нет, установить scores.setdefault("Eve", 0) # Добавит Eve: 0 scores.setdefault("Alice", 0) # НЕ изменит Alice (уже есть) ```
Что вернёт: {"a": 1, "b": 2}.pop("a")?
Итерация по словарям
Словари можно перебирать разными способами: по ключам, значениям или парам.
Способы итерации
Три варианта
```python user = {"name": "Алиса", "age": 25, "city": "Москва"} # По ключам (по умолчанию) for key in user: print(key) # name, age, city # По значениям for value in user.values(): print(value) # Алиса, 25, Москва # По парам (самый полезный) for key, value in user.items(): print(f"{key}: {value}") # name: Алиса # age: 25 # city: Москва ```
Практические примеры
Обработка данных
```python prices = {"apple": 100, "banana": 80, "orange": 120} # Найти самый дорогой товар max_item = max(prices, key=prices.get) print(f"Самый дорогой: {max_item}") # orange # Общая сумма total = sum(prices.values()) print(f"Всего: {total}") # 300 # Скидка 10% на все discounted = {k: v * 0.9 for k, v in prices.items()} print(discounted) # {'apple': 90.0, 'banana': 72.0, ...} ```
Что выводит цикл: for x in {"a": 1, "b": 2}?
Вложенные структуры
Словари могут содержать другие словари и списки - это основа для работы с JSON и сложными данными.
Вложенные словари
Как в JSON
```python user = { "name": "Алиса", "age": 25, "address": { "city": "Москва", "street": "Тверская", "building": 10 }, "hobbies": ["чтение", "программирование", "музыка"] } # Доступ к вложенным данным print(user["address"]["city"]) # Москва print(user["hobbies"][0]) # чтение # Изменение вложенных user["address"]["building"] = 15 user["hobbies"].append("спорт") ```
Список словарей
База данных в миниатюре
```python users = [ {"id": 1, "name": "Алиса", "active": True}, {"id": 2, "name": "Боб", "active": False}, {"id": 3, "name": "Вика", "active": True} ] # Найти активных пользователей active_users = [u["name"] for u in users if u["active"]] print(active_users) # ['Алиса', 'Вика'] # Найти пользователя по id def find_user(users, user_id): for user in users: if user["id"] == user_id: return user return None print(find_user(users, 2)) # {'id': 2, 'name': 'Боб', ...} ```
Как получить 'x' из: d = {"a": {"b": {"c": "x"}}}?
Dict Comprehensions
Как list comprehensions, но для словарей. Элегантный способ создания и трансформации.
Dict comprehension
Создание словаря в одну строку
```python # Квадраты чисел squares = {x: x**2 for x in range(5)} print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} # С условием even_squares = {x: x**2 for x in range(10) if x % 2 == 0} print(even_squares) # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64} # Из списка names = ["Alice", "Bob", "Charlie"] name_lengths = {name: len(name) for name in names} print(name_lengths) # {'Alice': 5, 'Bob': 3, 'Charlie': 7} # Инверсия словаря (ключ ↔ значение) original = {"a": 1, "b": 2, "c": 3} inverted = {v: k for k, v in original.items()} print(inverted) # {1: 'a', 2: 'b', 3: 'c'} ```
Что создаст: {x: x*2 for x in [1, 2, 3]}?
Связь с другими темами
Словари и объекты опираются на списки и циклы, и ведут к JSON, паттернам и тестам:
- Циклы — Итерация по .items(), .keys(), .values()
- Функции — **kwargs распаковываются в dict, словари передают конфигурацию
- Массивы и списки — list of dicts это типичная форма JSON, dict часто содержит list
- Паттерны проектирования — Strategy и Registry часто строятся на словарях имя-объект
- Тестирование — Fixtures и моки часто описываются словарями
Итог
- Sketchpad Сазерленда (MIT, 1963) задал идею объекта, Simula 67 (Нюгор и Даль) дала классы, наследование и виртуальные методы
- Smalltalk Алана Кэя в Xerox PARC (1972-1980) ввёл everything is an object и сообщения, MVC и рефакторинг родились там
- C++ (Страуструп, 1983) и Java (Гослинг, 1995) принесли OOP в индустрию, JavaScript (Эйх, 1995) пошёл через прототипы, классы добавили только в ES6 (2015)
- Python с 1991 года: всё объект, dict реализует namespace модуля и __dict__ экземпляра, type(x) работает для любого значения
- С Python 3.7 (2017) dict сохраняет порядок вставки, dataclasses (PEP 557, Eric V. Smith, 2018) убирают бойлерплейт в __init__/__repr__/__eq__
Связанные уроки
- prog-11-arrays — Массивы индексируют по позиции объекты по ключу
- prog-07-loops — Перебор ключей объекта использует циклы
- prog-13-patterns — Паттерны проектирования организуют объекты и их связи
- ds-06-hash-intro — Объект это хеш-таблица ключей и значений
- db-02-relational-model — Запись в таблице сопоставляет поля как объект
- alg-01-big-o