Справочник по Python · модуль 4 · словари, множества и кортежи
Глава 4.3
Методы словарей:
часть 1
Сначала — универсальные функции, которые принимают словарь как коллекцию. Главное при их использовании: решить, что именно обрабатывать — ключи, значения или пары. По умолчанию всегда берутся ключи, и это источник большинства ошибок.
- Категория
- Словари
- Уровень
- Базовый → средний
- Связанные темы
- Функции коллекций, сортировка, кортежи
- Зачем это знать
- Считать, сортировать и преобразовывать содержимое словаря
§1Функция или метод
Со словарём работают два вида инструментов, и различать их полезно по форме записи — как в главе 3.12.
| Форма | Пример | Смысл |
|---|---|---|
| универсальная функция | len(data) | работает со многими коллекциями |
| метод словаря | data.get(key) | описывает операцию именно словаря |
Функций из первой строки вы уже знаете почти все — они разбирались в главе 3.14 для списков. Со словарём они работают так же, но с одной оговоркой, которой посвящён следующий параграф.
§2По умолчанию — ключи
Когда словарь передают функции напрямую, перебираются его ключи. Не значения и не пары.
prices = {"book": 900, "pen": 120}
prices → "book", "pen" ключи
prices.values() → 900, 120 значения
prices.items() → ("book", 900), ("pen", 120) пары
Это правило действует везде: в for, в sorted(), в max(), в sum(), в list(). Прежде чем вызвать любую функцию, стоит задать себе один вопрос: с чем она должна работать — и дописать .values() или .items(), если ответ не «с ключами».
§3len(): число пар
prices = {"book": 900, "pen": 120, "bag": 2400}
print(len(prices))
3
Здесь двусмысленности нет: ключей и значений поровну, поэтому len(prices), len(prices.values()) и len(prices.items()) дадут одно и то же.
§4min() и max()
А вот здесь выбор области решает всё.
scores = {"Max": 82, "Anna": 95, "Oleg": 74}
print(max(scores))
print(max(scores.values()))
Oleg 95
Первая строка сравнивает строки-ключи по правилам главы 5.1 — то есть по кодовым точкам, и «Oleg» побеждает просто потому, что O идёт после A и M. К баллам это отношения не имеет. Вторая строка даёт максимальный балл, но уже не говорит, чей он.
§5Ключ с наибольшим значением
Обычно нужен именно третий вариант: не имя и не балл по отдельности, а имя того, у кого балл наибольший.
scores = {"Max": 82, "Anna": 95, "Oleg": 74}
print(max(scores, key=scores.get))
print(min(scores, key=scores.get))
Anna Oleg
Разберём запись. Перебираются по-прежнему ключи, но параметр key сообщает: «сравнивай их не напрямую, а по результату scores.get от каждого». Метод get передаётся без скобок — как значение, а не как вызов. Подробно этот приём разбирается в главе 6.10.
| Запись | Что вернёт |
|---|---|
max(scores) | наибольший ключ |
max(scores.values()) | наибольшее значение |
max(scores, key=scores.get) | ключ, у которого наибольшее значение |
§6sum()
Складывать имеет смысл значения, поэтому values() здесь обязателен.
cart = {"book": 900, "pen": 120, "bag": 2400}
print(sum(cart.values()))
3420
Забытый values() здесь как раз заметен сразу — Python попытается складывать строки с числом.
print(sum(cart))
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Повезло не всегда: если ключи числовые, ошибки не будет — просто сложатся не те числа.
§7sorted(): три варианта
Функция возвращает список и не меняет словарь. Что окажется в списке — снова зависит от области.
prices = {"book": 900, "pen": 120, "bag": 2400}
print(sorted(prices))
print(sorted(prices.values()))
print(sorted(prices.items()))
['bag', 'book', 'pen']
[120, 900, 2400]
[('bag', 2400), ('book', 900), ('pen', 120)]Третья строка сортирует пары, а кортежи сравниваются по первому элементу — то есть по ключу. Чтобы упорядочить по значению, нужен key.
§8Сортировка по значению
prices = {"book": 900, "pen": 120, "bag": 2400}
print(sorted(prices, key=prices.get))
print(sorted(prices.items(), key=lambda item: item[1]))
['pen', 'book', 'bag']
[('pen', 120), ('book', 900), ('bag', 2400)]Первый способ короче, но даёт только ключи. Второй сохраняет и значение: item — это пара (ключ, значение), а item[1] — её значение. Запись lambda разбирается в главе 6.9; пока достаточно читать её как «взять из пары второй элемент».
§9all() и any()
Эти функции сворачивают набор проверок в одно значение и почти всегда применяются к values().
completed = {"theory": True, "practice": False, "test": True}
print(all(completed.values()))
print(any(completed.values()))
False True
Истинность считается по правилам главы 3.16, поэтому годятся не только True и False: пустая строка, ноль и пустой список тоже ложны. У пустой коллекции результаты запоминают отдельно: all([]) — True, any([]) — False.
§10enumerate() и reversed()
Обе функции опираются на порядок добавления пар, который словарь сохраняет.
stages = {"draft": 1, "review": 2, "done": 3}
for number, key in enumerate(reversed(stages), start=1):
print(number, key)
1 done 2 review 3 draft
Здесь снова перебираются ключи: reversed(stages) разворачивает именно их. Для пар в обратном порядке нужен reversed(stages.items()).
§11zip(): из двух списков
Самый частый способ построить словарь из параллельных данных.
names = ["Max", "Anna", "Oleg"] scores = [82, 95, 74] print(dict(zip(names, scores)))
{'Max': 82, 'Anna': 95, 'Oleg': 74}zip() заканчивает работу вместе с самой короткой последовательностью — лишние элементы молча отбрасываются. Если списки должны быть одинаковой длины, это стоит проверить заранее.
§12map() и filter()
Эти функции тоже перебирают переданный объект, поэтому и здесь нужно указать область.
prices = {"book": 900, "pen": 120, "bag": 2400}
discounted = list(map(lambda price: price * 0.9, prices.values()))
expensive = dict(filter(lambda item: item[1] >= 500, prices.items()))
print(discounted)
print(expensive)
[810.0, 108.0, 2160.0]
{'book': 900, 'bag': 2400}Обратите внимание на разницу: map() по значениям теряет ключи, поэтому результат — список. filter() по парам их сохраняет, и результат можно снова свернуть в словарь. Ту же работу короче делает словарное включение из главы 4.10.
§13dict.fromkeys()
Метод создаёт словарь из набора ключей с одинаковым начальным значением.
fields = ("name", "email", "phone") form = dict.fromkeys(fields, None) print(form)
{'name': None, 'email': None, 'phone': None}Второй аргумент необязателен: без него все значения станут None. Обратите внимание на форму записи — dict.fromkeys() вызывается у самого типа, а не у существующего словаря.
§14Ловушка общего значения
Метод ставит во все пары один и тот же объект. Для None и чисел это незаметно — они неизменяемы. Для списка последствия наглядны.
groups = dict.fromkeys(["a", "b"], []) groups["a"].append(1) print(groups)
{'a': [1], 'b': [1]}Это та же история, что с двумя именами одного списка из главы 3.10: список создан один раз, а ссылок на него теперь две. Решение — словарное включение, которое создаёт новый список на каждом шаге.
groups = {key: [] for key in ["a", "b"]}
groups["a"].append(1)
print(groups)
{'a': [1], 'b': []}§15get() не создаёт ключ
Метод get() разбирался в главе 4.2, но одну его особенность стоит подчеркнуть отдельно: он только читает.
user = {"name": "Max"}
user.get("age", 0)
print(user)
{'name': 'Max'}Запасное значение существует только внутри выражения. Поэтому счётчик из главы 4.2 и записывают в две операции: counts[word] = counts.get(word, 0) + 1 — сначала прочитать, потом присвоить. Метод, который читает и сразу создаёт ключ, называется setdefault() и разбирается в главе 4.4.
§16items() — представление
items() возвращает не список, а представление — динамическое окно в словарь.
prices = {"book": 900, "pen": 120}
print(prices.items()[0])
TypeError: 'dict_items' object is not subscriptable
Для перебора циклом представления достаточно, и это самый частый случай.
for name, price in prices.items(): print(f"{name}: {price} ₽")
book: 900 ₽ pen: 120 ₽
Если нужен именно список — с индексами и срезами, — представление оборачивают: list(prices.items()). Чем «динамическое» отличается от обычного и почему это важно, разбирается в главе 4.4.
§17Частые ошибки
| Что написано | Что произойдёт | Как правильно |
|---|---|---|
sum(prices) при строковых ключах | TypeError: складываются ключи | sum(prices.values()) |
sum(prices) при числовых ключах | ошибки нет, сложатся не те числа | всегда указывать область явно |
max(scores) для лучшего балла | сравнятся имена по алфавиту | max(scores, key=scores.get) |
max(scores, key=scores.get()) | TypeError: метод вызван, а не передан | key=scores.get без скобок |
sorted(prices.items()) для порядка по цене | отсортируется по ключам | добавить key=lambda item: item[1] |
prices.items()[0] | TypeError: представление не индексируется | list(prices.items())[0] |
dict.fromkeys(keys, []) | все ключи разделят один список | {key: [] for key in keys} |
ждать, что get() создаст ключ | словарь не изменится | присвоить значение или setdefault() |
§18Проверьте себя
Что перебирает sorted(data), если data — словарь?
Ключи. Для значений нужен sorted(data.values()), для пар — sorted(data.items()).
Чем max(scores) отличается от max(scores, key=scores.get)?
Первое сравнивает сами ключи и вернёт последний по алфавиту. Второе тоже перебирает ключи, но сравнивает их по значениям, и вернёт ключ с наибольшим значением.
Почему key=scores.get пишется без скобок?
Передаётся сама функция, а вызывать её для каждого элемента будет max(). Со скобками метод выполнился бы сразу и без аргумента.
Меняет ли get() словарь?
Нет. Метод только читает существующее значение или возвращает запасное — новый ключ при этом не появляется.
Почему dict.fromkeys(keys, []) опасен?
Все ключи получают один и тот же список, поэтому изменение через один ключ видно через остальные.
Почему prices.items()[0] не работает?
items() возвращает представление, а не список: у него нет индексов. Нужен list(prices.items())[0].
§19Шпаргалка
len(data)sum(data.values())max(data.values())max(data, key=data.get)sorted(data, key=data.get)dict(zip(keys, values))dict.fromkeys(keys, 0)list(data.items())Итог главы
Универсальные функции работают со словарём так же, как со списком, но по умолчанию перебирают ключи — и решение «ключи, значения или пары» нужно принимать перед каждым вызовом. Параметр key позволяет перебирать одно, а сравнивать по другому: так находят ключ с наибольшим значением и сортируют словарь по значениям. dict.fromkeys() кладёт во все пары один и тот же объект, поэтому для изменяемых значений нужно включение, а get() только читает и ключей не создаёт.