ТЕОРИЯ И ПРАКТИКА
-
Ввод и вывод данных
- Задачи
-
Условия
- Задачи
-
Цикл for
- Задачи
-
Строки
- Задачи
-
Цикл while
- Задачи
-
Списки
- Задачи
-
Двумерные массивы
- Задачи
-
Словари
- Задачи
-
Множества
- Задачи
-
Функции и рекурсия
- Задачи
Занятие 8. Словари в питоне
Введение в словари
Словарь в Python: структура данных хеш-таблица
Словарь в Python (тип dict) — это реализация структуры данных, известной как хеш-таблица или ассоциативный массив. Его ключевое преимущество — невероятно быстрый доступ к элементам.
Внутри словарь работает так:
- Вы предоставляете ключ (например, слово "яблоко").
- Python вычисляет хеш от этого ключа — уникальное числовое представление.
- Это число используется как индекс для поиска ячейки в памяти, где хранится связанное с ключом значение (например, "красный фрукт").
Благодаря этому механизму поиск, добавление и удаление элементов происходят практически мгновенно, независимо от размера словаря.
Основные свойства словарей
Коллекция пар "ключ — значение"
Каждый элемент в словаре — это пара, состоящая из уникального ключа и связанного с ним значения. Это похоже на настоящий словарь, где ключ — это слово, а значение — его определение.
# Пример: словарь с информацией о пользователе
user = {
"name": "Alex",
"age": 30,
"is_admin": True
}
print(user)
Ключи уникальны
В одном словаре не может быть двух одинаковых ключей. Если вы попытаетесь добавить пару с уже существующим ключом, новое значение просто перезапишет старое.
config = {"host": "localhost", "port": 8080}
print(f"Изначальный порт: {config['port']}")
# Попытка добавить пару с существующим ключом "port"
config["port"] = 9000
print(f"Новый порт: {config['port']}")
print(f"Весь словарь: {config}")
Значения могут повторяться
В отличие от ключей, значения могут быть какими угодно и повторяться сколько угодно раз.
# Разные студенты могут иметь одинаковые оценки
grades = {
"Иван": 5,
"Мария": 4,
"Петр": 5
}
print(grades)
Отличие словаря от списка и множества
- Словарь vs Список (
list): В списках элементы упорядочены и доступны по числовому индексу (0, 1, 2...). В словарях элементы доступны по ключу, который может быть строкой, числом или другим неизменяемым типом. Поиск по ключу в словаре (O(1)) намного быстрее, чем поиск элемента в списке (O(n)). - Словарь vs Множество (
set): Множества хранят только уникальные значения (без ключей). Они хороши для проверки на наличие элемента и математических операций (объединение, пересечение), но не для хранения связанных данных.
Практические применения словарей
Словари используются повсеместно:
- Данные в формате JSON: Веб-API почти всегда возвращают данные в виде, который легко преобразуется в словари Python.
- Настройки конфигурации: Хранение параметров программы.
- Представление объектов: Например, пользователя, продукта, заказа.
- Кэширование: Хранение результатов "дорогих" вычислений.
- Подсчёт частоты: Быстрый подсчёт уникальных элементов в коллекции.
Создание словарей Python
Словарь пустой: создание
Через {}
Самый распространённый и предпочтительный способ — использование фигурных скобок.
empty_dict_1 = {}
print(f"Тип: {type(empty_dict_1)}, Содержимое: {empty_dict_1}")
Через функцию dict()
Можно также использовать встроенную функцию dict().
empty_dict_2 = dict()
print(f"Тип: {type(empty_dict_2)}, Содержимое: {empty_dict_2}")
Словарь с заданными значениями
Явное указание пар ключ-значение
Это основной способ создания словаря с начальными данными.
person = {
"first_name": "John",
"last_name": "Doe",
"age": 25
}
print(person)
Через функцию dict() с именованными аргументами
Этот способ удобен, когда ключи являются простыми строками без пробелов и спецсимволов.
person_alt = dict(first_name="Jane", last_name="Doe", age=28)
print(person_alt)
Создание словаря из списков с помощью zip()
Если у вас есть два списка (один для ключей, другой для значений), их можно "сшить" в словарь с помощью zip() и dict().
keys = ["brand", "model", "year"]
values = ["Toyota", "Camry", 2022]
car = dict(zip(keys, values))
print(car)
Генераторы словарей (dict comprehension)
Это мощный и лаконичный способ создания словарей на основе любой итерируемой последовательности.
# Создать словарь, где ключ - число, а значение - его квадрат
squares = {x: x**2 for x in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Доступ к элементам словаря
Получение значения по ключу через []
Основной способ доступа к значению — указать его ключ в квадратных скобках. Внимание: если ключа не существует, это вызовет ошибку KeyError.
user = {"name": "Alex", "age": 30}
print(user["name"]) # Выведет "Alex"
# Следующая строка вызовет ошибку KeyError, так как ключа 'city' нет
# print(user["city"])
Получение значения через метод get()
Метод get() — безопасный способ доступа. Он не вызывает ошибку, если ключа нет.
Без значения по умолчанию
Если ключ не найден, get() по умолчанию возвращает None.
user = {"name": "Alex", "age": 30}
city = user.get("city")
print(f"Имя: {user.get('name')}")
print(f"Город: {city}") # Выведет None, ошибки не будет
С указанием значения по умолчанию
Вторым аргументом в get() можно передать значение, которое вернётся, если ключ не будет найден.
user = {"name": "Alex", "age": 30}
# Если ключ 'city' не найден, вернуть строку 'Не указан'
city = user.get("city", "Не указан")
print(f"Город: {city}") # Выведет 'Не указан'
Обработка ошибок доступа к несуществующему ключу
Если вы используете [], но не уверены в наличии ключа, используйте конструкцию try...except.
user = {"name": "Alex", "age": 30}
try:
city = user["city"]
print(f"Город: {city}")
except KeyError:
print("Ключ 'city' не найден в словаре.")
Изменение и добавление элементов
Присваивание значения по ключу
Синтаксис для изменения существующего элемента и добавления нового абсолютно одинаков.
Изменение существующего
Если ключ уже есть в словаре, его значение будет обновлено.
user = {"name": "Alex", "age": 30}
print(f"Старый возраст: {user['age']}")
user["age"] = 31 # Обновляем значение по ключу 'age'
print(f"Новый возраст: {user['age']}")
Добавление нового
Если ключа в словаре нет, будет создана новая пара "ключ-значение".
user = {"name": "Alex", "age": 30}
print(f"Словарь до добавления: {user}")
user["email"] = "alex@example.com" # Добавляем новую пару
print(f"Словарь после добавления: {user}")
Удаление элементов из словаря
Метод pop()
Удаляет пару по указанному ключу и возвращает удалённое значение. Если ключ не найден, вызывает KeyError.
user = {"name": "Alex", "age": 30, "email": "alex@example.com"}
removed_age = user.pop("age")
print(f"Удаленное значение: {removed_age}")
print(f"Словарь после удаления: {user}")
# Можно указать значение по умолчанию, чтобы избежать ошибки
removed_city = user.pop("city", "Город не был указан")
print(f"Попытка удалить несуществующий ключ: {removed_city}")
Метод popitem()
Удаляет и возвращает последнюю добавленную пару (ключ, значение) в виде кортежа. Работает по принципу LIFO (Last-In, First-Out). Если словарь пуст, вызывает KeyError.
user = {"name": "Alex", "email": "alex@example.com"}
last_item = user.popitem()
print(f"Удаленная пара: {last_item}")
print(f"Словарь после popitem: {user}")
Оператор del
Удаляет пару по ключу. Ничего не возвращает. Если ключ не найден, вызывает KeyError.
user = {"name": "Alex", "age": 30}
del user["age"]
print(f"Словарь после del: {user}")
Метод clear()
Удаляет все элементы из словаря, делая его пустым.
user = {"name": "Alex", "age": 30}
user.clear()
print(f"Словарь после clear: {user}")
Методы словаря Python: доступ и операции
Метод get()
Как мы уже видели, безопасно получает значение по ключу, возвращая None или значение по умолчанию, если ключ отсутствует.
Метод keys()
Возвращает специальный объект-представление (dict_keys), содержащий все ключи словаря. Его можно перебирать в цикле или преобразовать в список.
car = {"brand": "Ford", "model": "Mustang", "year": 1964}
all_keys = car.keys()
print(f"Объект ключей: {all_keys}")
print(f"Ключи в виде списка: {list(all_keys)}")
Получение значений из словаря: values()
Возвращает объект-представление (dict_values) со всеми значениями словаря.
car = {"brand": "Ford", "model": "Mustang", "year": 1964}
all_values = car.values()
print(f"Объект значений: {all_values}")
print(f"Значения в виде списка: {list(all_values)}")
Методы, возвращаемые items() dictionary
Возвращает объект-представление (dict_items) с парами (ключ, значение) в виде кортежей. Это самый удобный способ для перебора словаря целиком.
car = {"brand": "Ford", "model": "Mustang", "year": 1964}
all_items = car.items()
print(f"Объект пар: {all_items}")
print(f"Пары в виде списка: {list(all_items)}")
Метод update()
Обновляет словарь, добавляя пары ключ-значение из другого словаря или итерируемого объекта. Если ключи совпадают, значения перезаписываются.
user_info = {"name": "Alice", "age": 25}
user_contacts = {"email": "alice@email.com", "age": 26} # возраст будет обновлен
user_info.update(user_contacts)
print(user_info)
Метод fromkeys()
Создаёт новый словарь из переданной последовательности ключей. Всем ключам присваивается одно и то же значение (по умолчанию None).
keys = ['a', 'b', 'c']
# Создать словарь с ключами из списка и значением 0 для каждого
new_dict = dict.fromkeys(keys, 0)
print(new_dict) # {'a': 0, 'b': 0, 'c': 0}
# Если значение не указать, будет None
default_dict = dict.fromkeys(keys)
print(default_dict) # {'a': None, 'b': None, 'c': None}
Перебор словаря
Перебор только ключей
Это поведение по умолчанию при итерации по словарю.
person = {"name": "Bob", "age": 42, "city": "New York"}
for key in person:
print(key)
# или явно
for key in person.keys():
print(key)
Перебор только значений
Для этого используется метод values().
person = {"name": "Bob", "age": 42, "city": "New York"}
for value in person.values():
print(value)
Перебор пар ключ-значение через items()
Самый распространённый и удобный способ. Позволяет сразу получить и ключ, и значение на каждой итерации.
person = {"name": "Bob", "age": 42, "city": "New York"}
for key, value in person.items():
print(f"Ключ: {key}, Значение: {value}")
Перебор с условием и фильтрацией
Циклы легко комбинируются с условными операторами if.
products = {"apple": 50, "banana": 20, "orange": 80, "milk": 120}
# Найти все продукты, цена которых больше 60
for product, price in products.items():
if price > 60:
print(f"{product} стоит {price}")
Вложенные словари в Python
Структура словаря в словаре
Значением в словаре может быть другой словарь. Это позволяет создавать сложные, иерархические структуры данных.
# Словарь пользователей, где каждый пользователь - это тоже словарь
users = {
"user1": {
"name": "Alice",
"email": "alice@example.com"
},
"user2": {
"name": "Bob",
"email": "bob@example.com"
}
}
print(users)
Доступ к элементам вложенного словаря
Доступ осуществляется по цепочке ключей.
users = {
"user1": { "name": "Alice", "email": "alice@example.com" },
"user2": { "name": "Bob", "email": "bob@example.com" }
}
# Получить email пользователя user2
email_bob = users["user2"]["email"]
print(email_bob) # bob@example.com
Изменение и добавление элементов во вложенные словари
Принцип тот же: добираемся до нужного места по цепочке ключей и присваиваем новое значение.
users = {
"user1": { "name": "Alice", "email": "alice@example.com" }
}
# Изменить имя пользователя user1
users["user1"]["name"] = "Alicia"
# Добавить возраст пользователю user1
users["user1"]["age"] = 30
print(users)
Перебор вложенных словарей
Используются вложенные циклы.
users = {
"user1": { "name": "Alice", "email": "alice@example.com" },
"user2": { "name": "Bob", "email": "bob@example.com" }
}
# Перебрать всех пользователей и их данные
for user_id, user_data in users.items():
print(f"ID пользователя: {user_id}")
for key, value in user_data.items():
print(f" {key}: {value}")
Практические задачи
Подсчёт частоты символов в строке
Классическая задача, идеально решаемая с помощью словаря.
text = "hello world"
frequency = {}
for char in text:
# Используем get() с значением по умолчанию 0
frequency[char] = frequency.get(char, 0) + 1
print(frequency) # {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}
Группировка данных по общему признаку
Например, сгруппируем список людей по городам.
people = [
{"name": "Alice", "city": "New York"},
{"name": "Bob", "city": "London"},
{"name": "Charlie", "city": "New York"},
{"name": "Diana", "city": "London"}
]
grouped_by_city = {}
for person in people:
city = person["city"]
# Если города еще нет в словаре, создаем для него пустой список
if city not in grouped_by_city:
grouped_by_city[city] = []
# Добавляем человека в список его города
grouped_by_city[city].append(person["name"])
print(grouped_by_city) # {'New York': ['Alice', 'Charlie'], 'London': ['Bob', 'Diana']}
Объединение двух словарей
Через update()
Метод update() изменяет исходный словарь.
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
dict1.update(dict2) # dict1 будет изменен
print(dict1) # {'a': 1, 'b': 3, 'c': 4}
Через оператор **
Оператор распаковки ** (Python 3.5+) позволяет создать новый объединенный словарь, не изменяя исходные.
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
# При совпадении ключей будет взято значение из последнего словаря (dict2)
merged_dict = {**dict1, **dict2}
print(merged_dict) # {'a': 1, 'b': 3, 'c': 4}
print(dict1) # {'a': 1, 'b': 2} - остался без изменений
Инвертирование словаря
Обратная замена ключей и значений
Простой случай, когда все значения уникальны.
original = {'a': 1, 'b': 2, 'c': 3}
inverted = {value: key for key, value in original.items()}
print(inverted) # {1: 'a', 2: 'b', 3: 'c'}
Обработка одинаковых значений
Если значения не уникальны, простая инверсия приведет к потере данных. Правильное решение — группировать ключи в список.
original = {'a': 1, 'b': 2, 'c': 1} # Значение '1' повторяется
inverted_grouped = {}
for key, value in original.items():
if value not in inverted_grouped:
inverted_grouped[value] = []
inverted_grouped[value].append(key)
print(inverted_grouped) # {1: ['a', 'c'], 2: ['b']}
Генерация словарей (dict comprehension)
Простые генераторы: {x: f(x) for x in ...}
Лаконичный синтаксис для создания словарей.
# Словарь {1: 1, 2: 4, 3: 9, ...}
squares = {x: x * x for x in range(1, 6)}
print(squares)
Генераторы с условием
Можно добавить if для фильтрации элементов.
# Создать словарь только для нечетных чисел
odd_squares = {x: x * x for x in range(1, 10) if x % 2 != 0}
print(odd_squares) # {1: 1, 3: 9, 5: 25, 7: 49, 9: 81}
Генераторы на основе zip() или enumerate()
Можно использовать другие функции для получения пар.
# zip
keys = ['name', 'age']
values = ['Tom', 33]
person = {k: v for k, v in zip(keys, values)}
print(person) # {'name': 'Tom', 'age': 33}
# enumerate
items = ['apple', 'banana', 'cherry']
indexed_items = {index: value for index, value in enumerate(items)}
print(indexed_items) # {0: 'apple', 1: 'banana', 2: 'cherry'}
Полезные приёмы и трюки
Проверка существования ключа через in
Самый быстрый и "питоничный" (Pythonic) способ проверить, есть ли ключ в словаре.
user = {"name": "Frank", "age": 50}
if "age" in user:
print("Ключ 'age' существует.")
if "city" not in user:
print("Ключ 'city' отсутствует.")
Использование setdefault()
Метод setdefault() похож на get(), но с одним важным отличием: если ключ отсутствует, он не только возвращает значение по умолчанию, но и добавляет в словарь новую пару с этим ключом и значением. Это очень удобно для инициализации коллекций.
# Классический пример группировки с setdefault()
data = [("fruits", "apple"), ("vegetables", "carrot"), ("fruits", "banana")]
grouped = {}
for category, item in data:
# Если 'category' нет, создастся ключ со значением [] и вернется [].
# Если есть, просто вернется существующий список.
# В любом случае, мы можем сразу делать .append()
grouped.setdefault(category, []).append(item)
print(grouped) # {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}
Подсчёт и накопление значений
Использование словарей как счётчиков
Эти два пункта тесно связаны. Словарь — идеальный инструмент для подсчёта.
from collections import Counter
# Простой способ (уже показывали)
words = ["apple", "orange", "apple", "banana", "orange", "apple"]
word_counts = {}
for word in words:
word_counts[word] = word_counts.get(word, 0) + 1
print(word_counts)
# Продвинутый способ с использованием специального класса Counter
word_counts_pro = Counter(words)
print(word_counts_pro) # Counter({'apple': 3, 'orange': 2, 'banana': 1})
# Counter - это подкласс dict, у него есть все те же методы и еще свои.
print(word_counts_pro.most_common(2)) # [('apple', 3), ('orange', 2)]
Использование словарей как хранилищ (например, кэш)
Кэширование (или мемоизация) — это сохранение результатов выполнения функции для предотвращения повторных вычислений с теми же аргументами.
import time
# Словарь для кэша
fib_cache = {}
def fibonacci(n):
# Если значение уже есть в кэше, вернуть его
if n in fib_cache:
return fib_cache[n]
# Иначе, вычислить
if n <= 1:
result = n
else:
result = fibonacci(n - 1) + fibonacci(n - 2)
# Сохранить результат в кэш перед возвратом
fib_cache[n] = result
return result
start_time = time.time()
print(fibonacci(35))
print(f"Время выполнения: {time.time() - start_time:.4f} секунд")
# Второй вызов будет почти мгновенным, так как все значения уже в кэше
start_time = time.time()
print(fibonacci(35))
print(f"Время второго вызова: {time.time() - start_time:.4f} секунд")
Преобразование словаря
Ключи/значения/пары в список
my_dict = {'a': 1, 'b': 2, 'c': 3}
key_list = list(my_dict.keys()) # или просто list(my_dict)
value_list = list(my_dict.values())
item_list = list(my_dict.items()) # список кортежей
print(f"Ключи: {key_list}")
print(f"Значения: {value_list}")
print(f"Пары: {item_list}")
В JSON (сериализация)
JSON (JavaScript Object Notation) — текстовый формат обмена данными. Структура словарей Python идеально ему соответствует. Процесс преобразования в строку JSON называется сериализацией.
import json
user = {
"name": "Иван Петров",
"age": 30,
"is_active": True,
"courses": ["Python", "Git"],
"passport": None
}
# Преобразовать словарь в строку JSON
# ensure_ascii=False для корректного отображения кириллицы
# indent=4 для красивого форматирования с отступами
json_string = json.dumps(user, ensure_ascii=False, indent=4)
print(json_string)
# Обратное преобразование (десериализация)
back_to_dict = json.loads(json_string)
print(back_to_dict)
Из других структур (например, list of tuples)
Словарь можно создать из любой последовательности, элементы которой сами являются последовательностями из двух элементов (ключ, значение).
list_of_tuples = [('a', 1), ('b', 2), ('c', 3)]
my_dict = dict(list_of_tuples)
print(my_dict)
Расширенные структуры
Словари с вложенными списками
Очень распространённая структура для хранения связанных коллекций.
user_permissions = {
"admin": ["create_user", "delete_user", "edit_content"],
"editor": ["edit_content"],
"viewer": ["view_content"]
}
# Проверить, может ли админ удалять пользователей
can_delete = "delete_user" in user_permissions["admin"]
print(f"Админ может удалять пользователей: {can_delete}")
Словари с множествами
Используйте множество (set) в качестве значения, если вам важна уникальность элементов и быстрая проверка на вхождение.
user_tags = {
"post1": {"python", "web", "django"},
"post2": {"python", "data-science"},
"post3": {"web", "css"}
}
# Найти все посты с тегом 'python'
python_posts = [post_id for post_id, tags in user_tags.items() if "python" in tags]
print(f"Посты с тегом 'python': {python_posts}")
Словари с функциями как значениями
Функции в Python являются объектами первого класса, их можно хранить в словарях. Это позволяет реализовать, например, паттерн "фабрика" или "диспетчер".
def add(a, b):
return a + b
def subtract(a, b):
return a - b
# Словарь, который сопоставляет строковые операторы с функциями
operations = {
"+": add,
"-": subtract
}
operator = "+"
x, y = 10, 5
# Выбрать нужную функцию из словаря и вызвать ее
result = operations[operator](x, y)
print(f"{x} {operator} {y} = {result}")
Распространённые ошибки и подводные камни
Ошибка KeyError в Python
Возникает при попытке доступа по несуществующему ключу через квадратные скобки [].
Как избежать:
- Использовать
dict.get(). - Проверять наличие ключа через
if key in my_dict. - Использовать
try...except KeyError.
Использование изменяемых объектов как ключей
Ключи словаря должны быть неизменяемыми (immutable) и хешируемыми (hashable). Это строки, числа, кортежи (tuple).
Списки (list), множества (set) и другие словари (dict) не могут быть ключами, так как они изменяемы.
valid_dict = {}
valid_dict["string_key"] = 1
valid_dict[123] = 2
valid_dict[(1, 2)] = 3 # Кортеж - неизменяемый, можно использовать как ключ
print(f"Рабочий словарь: {valid_dict}")
# Следующий код вызовет ошибку TypeError: unhashable type: 'list'
# invalid_dict = {}
# invalid_dict[[1, 2]] = "не сработает"
Поверхностное vs глубокое копирование словарей
Это важная концепция при работе с вложенными структурами.
- Поверхностное копирование (
.copy()): Создаётся новый словарь, но в него копируются ссылки на вложенные объекты. Изменение вложенного объекта в копии затронет и оригинал. - Глубокое копирование (
copy.deepcopy()): Создаётся полная, независимая копия, включая все вложенные объекты.
import copy
original = {
"a": 1,
"b": [10, 20, 30] # Вложенный изменяемый список
}
# Поверхностная копия
shallow = original.copy()
shallow["b"].append(40) # Изменяем список в копии
print(f"Оригинал после изменения поверхностной копии: {original}") # Оригинал тоже изменился!
# Глубокая копия
original = {"a": 1, "b": [10, 20, 30]} # Восстановим
deep = copy.deepcopy(original)
deep["b"].append(40) # Изменяем список в копии
print(f"Оригинал после изменения глубокой копии: {original}") # Оригинал не изменился
Производительность и оптимизация
Быстродействие операций со словарями
Благодаря хеш-таблицам, основные операции (добавление, получение, удаление элемента по ключу) в среднем выполняются за константное время, O(1). Это означает, что время выполнения не зависит от количества элементов в словаре.
Сравнение с другими структурами данных
- Словарь (поиск по ключу): O(1)
- Список (поиск по значению): O(n) - нужно перебрать все элементы.
- Множество (проверка наличия элемента): O(1)
Вывод: если вам нужен быстрый поиск по уникальному идентификатору, словарь — лучший выбор.
Кэширование результатов с помощью словарей
Как было показано в ранее, словари — это идеальный инструмент для реализации кэширования (мемоизации), что является одной из ключевых техник оптимизации производительности.