Словари
Словарь
Словарь (dict) хранит пары ключ → значение и находит значение по ключу в среднем за O(1) — независимо от размера.
user = {"name": "Анна", "age": 28, "city": "Москва"}
print(user["name"])
user["age"] = 29 # изменить
user["email"] = "a@mail.ru" # добавить
print(user, len(user))
print("city" in user, "phone" in user) # проверка ключа
Другие способы создать словарь:
a = dict(name="Анна", age=28) # именованные аргументы
b = dict([("x", 1), ("y", 2)]) # из пар
c = dict(zip(["a", "b", "c"], [1, 2, 3])) # из двух списков
d = {n: n * n for n in range(5)} # словарное включение
e = dict.fromkeys(["read", "write"], False) # одинаковые значения
print(a, b, c, d, e, sep="\n")
Безопасный доступ
config = {"debug": True}
print(config.get("debug"))
print(config.get("timeout")) # None
print(config.get("timeout", 30)) # значение по умолчанию
try:
config["timeout"]
except KeyError as e:
print("KeyError:", e)
Изменение и удаление
d = {"a": 1, "b": 2, "c": 3}
d.update({"b": 20, "d": 4}) # обновить несколько
print(d)
print(d.pop("a"), d.pop("zzz", "нет такого")) # удалить и вернуть
del d["c"]
print(d.popitem()) # удалить последнюю добавленную пару
print(d)
merged = {"a": 1} | {"b": 2} # Python 3.9+: объединение
merged |= {"a": 100} # обновить на месте
print(merged, {**merged, "c": 3}) # и старый способ — распаковка
setdefault: значение по умолчанию с добавлением
index = {}
for word in ["кот", "кит", "пёс", "паук"]:
index.setdefault(word[0], []).append(word) # если ключа нет — создать []
print(index)
setdefault(k, default) возвращает значение по ключу, а если ключа нет — вставляет default и возвращает его. Ещё удобнее — collections.defaultdict (следующие уроки).
Перебор
prices = {"кофе": 199, "чай": 99, "сок": 150}
for key in prices: # по ключам
print(key, end=" ")
print()
for name, price in prices.items(): # пары
print(f"{name}: {price}")
print(list(prices.keys()), list(prices.values()), sum(prices.values()))
print(max(prices, key=prices.get)) # ключ с максимальным значением
keys(), values(), items() возвращают представления (views): они не копируют данные и отражают изменения словаря. keys() и items() поддерживают операции множеств:
a = {"x": 1, "y": 2}
b = {"y": 3, "z": 4}
print(a.keys() & b.keys(), a.keys() - b.keys(), a.keys() | b.keys())
Не меняйте размер словаря во время перебора
for k in d: del d[k] → RuntimeError: dictionary changed size during iteration. Перебирайте копию ключей: for k in list(d):.
Порядок
С Python 3.7 словарь гарантированно сохраняет порядок вставки. Изменение значения существующего ключа порядок не меняет; удаление и повторная вставка — переносит ключ в конец.
d = {"b": 1, "a": 2}
d["c"] = 3
d["b"] = 10
print(list(d))
print(dict(sorted(d.items()))) # отсортировать по ключу
print(dict(sorted(d.items(), key=lambda kv: kv[1]))) # по значению
Ключи
Ключ должен быть хешируемым (неизменяемым): числа, строки, кортежи из хешируемых, frozenset. Значения — любые.
d = {}
d[(1, 2)] = "кортеж — можно"
print(d)
d[[1, 2]] = "список — нельзя" # TypeError: unhashable type: 'list'
1 и True — один ключ
{1: "a", True: "b"} даёт {1: "b"}: 1 == True и hash(1) == hash(True), поэтому это один и тот же ключ. Любимый вопрос «что выведет код».
Вложенные словари и JSON
company = {
"name": "Рога и копыта",
"employees": [
{"name": "Анна", "skills": ["Python", "SQL"]},
{"name": "Борис", "skills": ["Go"]},
],
}
print(company["employees"][0]["skills"][1])
for emp in company["employees"]:
print(emp["name"], ", ".join(emp["skills"]))
import json
print(json.dumps(company, ensure_ascii=False, indent=2))
Словари и списки один в один соответствуют JSON — поэтому в вебе с ними работают постоянно.
Как устроен словарь (кратко)
Внутри — хеш-таблица. Для ключа вычисляется hash(key), по нему находится ячейка. Поэтому поиск, вставка и удаление — O(1) в среднем (в худшем случае при множестве коллизий — O(n)). Подробно разберём в уроке «Хеширование: как устроены dict и set».
| Операция | Сложность |
|---|---|
d[k], d[k] = v, k in d, del d[k], get |
O(1) в среднем |
перебор, copy(), values() в список |
O(n) |
Итог
dict— ключ → значение, поиск O(1) в среднем; порядок вставки сохраняется.d[k]бросаетKeyError,d.get(k, default)— нет;setdefaultвставляет значение по умолчанию.- Перебор:
for k, v in d.items(); не меняйте размер во время перебора. - Объединение:
a | b,{**a, **b},update. - Ключи — только хешируемые объекты.
Практика решено 0 из 6
Напишите функцию word_count(text), которая возвращает словарь «слово → сколько раз встретилось».
Слова разделены пробелами, регистр не учитывать. Не используйте collections.Counter.
Какие тесты проверяют код (3)
assert word_count("кот пёс Кот") == {"кот": 2, "пёс": 1}
assert word_count("") == {}
assert word_count("a a a") == {"a": 3}
def word_count(text):
counts = {}
for word in text.lower().split():
counts[word] = counts.get(word, 0) + 1
return counts
Чем d["x"] отличается от d.get("x"), если ключа нет?
Напишите функцию group_by_length(words), которая группирует слова по длине:
возвращает словарь «длина → список слов в исходном порядке».
Какие тесты проверяют код (2)
assert group_by_length(["кот", "пёс", "слон", "ёж"]) == {3: ["кот", "пёс"], 4: ["слон"], 2: ["ёж"]}
assert group_by_length([]) == {}
def group_by_length(words):
groups = {}
for w in words:
groups.setdefault(len(w), []).append(w)
return groups
Напишите функцию invert(d), которая меняет местами ключи и значения. Если несколько ключей
имеют одинаковое значение, в результате должен остаться последний из них.
Какие тесты проверяют код (3)
assert invert({"a": 1, "b": 2}) == {1: "a", 2: "b"}
assert invert({"a": 1, "b": 1}) == {1: "b"}
assert invert({}) == {}
def invert(d):
return {v: k for k, v in d.items()}
Что можно использовать в качестве ключа словаря?
Сохраняет ли обычный dict порядок добавления ключей?
Войдите, чтобы отмечать пройденные уроки.