Перебор словаря: keys, values и items
Три представления словаря — keys, values, items: перебираем цены в цикле for, распаковываем пары в две переменные, считаем сумму и фильтруем дешёвые позиции.
Редакция Питоники
До сих пор мы работали со словарём по одному ключу: проверяли наличие оператором in в уроке 7, добавляли и меняли пары в уроке 5. Но склад из двадцати товаров по одному не обходят. Пора научиться проходить по всем парам сразу — для этого у словаря есть три представления: keys, values и items.
Три представления: keys, values, items
stock = {'молоко': 12, 'хлеб': 5, 'сыр': 3}
print(stock.keys())
print(stock.values())
print(stock.items())
dict_keys(['молоко', 'хлеб', 'сыр'])
dict_values([12, 5, 3])
dict_items([('молоко', 12), ('хлеб', 5), ('сыр', 3)])Методы вернули не совсем списки, а представления: dict_keys, dict_values, dict_items. Печатаются они как списки и работают везде, где ждут последовательность: в цикле for, в функциях list(), len() и sum(). Ключи, значения и пары — три взгляда на один и тот же словарь: данные не копируются, меняется лишь то, что видно из цикла.
Цикл for по ключам
prices = {'молоко': 89, 'хлеб': 42, 'сыр': 590}
for name in prices:
print(name, prices[name])
молоко 89 хлеб 42 сыр 590
Словарь в заголовке цикла сам отдаёт ключи — писать prices.keys() не обязательно, хотя и можно. На каждом шаге значение достаётся по ключу в квадратных скобках, и весь прайс напечатан. Это родня оператору in из урока 7: оба идут по ключам. Но запись prices[name] на каждом шаге громоздкая — для пар есть путь короче: метод items() отдаёт пары-кортежи, а две переменные в заголовке цикла распаковывают каждую пару — ключ в первую, значение во вторую. А когда нужны только величины, представление values() и функция sum() считают итог без всякого цикла.
Пары с распаковкой и суммы
prices = {'молоко': 89, 'хлеб': 42, 'сыр': 590}
for name, price in prices.items():
print(name, '-', price, 'руб.')
молоко - 89 руб. хлеб - 42 руб. сыр - 590 руб.
stock = {'молоко': 12, 'хлеб': 5, 'сыр': 3}
total = sum(stock.values())
print(total)
print(len(stock))
20 3
Представления — это окна, а не копии
Тонкий момент, который обсуждают на собеседованиях: stock.keys() возвращает не список, а представление — «окно» в словарь. Список копирует данные, а окно ссылается на них. Разница видна сразу: если взять окно и потом добавить в словарь новую пару, в окне она появится тоже.
stock = {'молоко': 12, 'хлеб': 5}
view = stock.keys()
print(list(view))
stock['сыр'] = 8
print(list(view))
['молоко', 'хлеб'] ['молоко', 'хлеб', 'сыр']
Второй вывод длиннее первого — потому что это тот же объект-обзор, просто посмотренный позже. Практическое следствие: если окно нужно сохранить «как было», берите list(stock.keys()) — получится обычный список с копией данных. Для циклов и подсчётов разницы нет, разве что list() на пустом словаре даст список, а не пустое окно, и его можно будет спокойно менять.
Фильтр и подсчёт за один проход
Три представления словаря придуманы не просто так. С их помощью собирают вещи, которые на списках пришлось бы писать в несколько строк. Разберём три задачи на одном словаре остатков.
stock = {'молоко': 12, 'хлеб': 5, 'сыр': 8}
for name, amount in stock.items():
print(f'{name}: {amount}')
cheap = {name: amount for name, amount in stock.items() if amount < 10}
print(cheap)
print(sum(stock.values()))
print(max(stock.values()))
молоко: 12
хлеб: 5
сыр: 8
{'хлеб': 5, 'сыр': 8}
25
12Первый цикл печатает пары через f-строку. Второй — собирает новый словарь из дешёвых позиций: фигурные скобки внутри цикла строят словарь, условие после if отбирает пары. Третий и четвёртый — готовые функции: sum складывает все значения, max находит наибольшее. Ни одна из этих задач не требует отдельного счётчика или ручного сложения в цикле.
Про f-строку стоит сказать отдельно. Строчный вариант print(name, '-', amount) работает и в уроке 1 мы им пользовались, но f-строка удобнее, когда значение вставляется внутрь текста: f'{name}: {amount}'. Плюс она не создаёт лишних строк при конкатенации и читается как шаблон. Словарь в f-строке тоже можно вывести целиком — f'{stock}' даст то же самое, что print(stock), только встроенную в строку.
stock = {'молоко': 12, 'хлеб': 5}
try:
for name in stock:
del stock[name]
except RuntimeError as e:
print('RuntimeError:', e)
RuntimeError: dictionary changed size during iteration
Обратите внимание: первая же итерация удаляет пару, и обход тут же падает. Никакого предупреждения заранее Python не выдаёт — ошибка прилетает в момент, когда размер таблицы изменился посреди прохода. Если в цикле меняют только значения, а не состав словаря, — всё в порядке: stock[name] = 0 размер не трогает и обход не ломает.
Сколько стоит перебор
Ещё одна вещь, которую стоит понимать про обход. Словарь отдаёт пары в порядке вставки — это не совпадение, а гарантия: начиная с Python 3.7 порядок словаря официально зафиксирован и не изменится. Именно на этом стоят многие решения — например, «первая пара в словаре» означает «первая добавленная», и на неё можно опираться.
Но если вам нужен другой порядок, придётся попросить Python явно: sorted(stock) даст ключи по алфавиту, sorted(stock.items(), key=lambda pair: pair[1]) — пары по значению. Эти вызовы создают новый список, то есть тратят память и время. На словаре из пары сотен записей это незаметно; на десятках тысяч — уже ощутимо. Практическое правило: сортируйте на выходе, а не каждый раз, когда кто-то читает словарь.
stock = {'молоко': 12, 'хлеб': 5, 'сыр': 8}
print(sorted(stock))
print(sorted(stock.items(), key=lambda pair: pair[1]))
print(sorted(stock.items(), key=lambda pair: -pair[1]))
['молоко', 'сыр', 'хлеб']
[('хлеб', 5), ('сыр', 8), ('молоко', 12)]
[('молоко', 12), ('сыр', 8), ('хлеб', 5)]Первый вызов сортирует ключи по алфавиту. Второй — пары по значению, по возрастанию: лямбда pair[1] говорит, что сравнивать надо по второму элементу кортежа. Третий — то же самое, но по убыванию: знак минус перед pair[1] переворачивает порядок. Подробнее про сортировку словарей — в уроке 13.
Обратный словарь: ключом стало значение
Изредко, но регулярно нужно перевернуть словарь: сделать из значений ключи. Типичный случай — найти товар по его цене или сотрудника по номеру телефона. Делается это просто: заводим новый словарь и в цикле меняем роли местами.
prices = {'молоко': 89, 'хлеб': 42, 'сыр': 590}
by_price = {}
for name, price in prices.items():
by_price[price] = name
print(by_price)
print(by_price[42])
{89: 'молоко', 42: 'хлеб', 590: 'сыр'}
хлебОбратный словарь удобен, когда ищут по значению, но платить приходится за память: второй словарь занимает примерно столько же места, сколько первый. И ещё одна оговорка — если значения повторяются, выживает только последний: при одинаковой цене у двух товаров в обратном словаре останется один, а второй исчезнет без всякой ошибки.
| Задача | Что писать |
|---|---|
| Нужны только ключи | for name in prices: — или for name in prices.keys(): |
| Нужны только значения | for amount in prices.values(): — сумма через sum(prices.values()) |
| Нужны и ключ, и значение | for name, price in prices.items(): |
| Нужно отсортировать | sorted(prices.items(), key=lambda pair: pair[1]) |
Правило выбора простое: берите самое узкое представление, которое решает задачу. Обращение к prices[name] внутри цикла по ключам — лишний поиск по хешу на каждом шаге; парам из items() он не нужен.
Что дальше
Словарь теперь можно обойти тремя способами и посчитать по нему что угодно. Дальше — структура посложнее: когда под одним ключом лежит целый словарь. В уроке 9 разбираем вложенные словари, доступ по цепочке ключей и то, почему два уровня — это предел разумного. А если данные надо сгруппировать по ключу в списки, — урок 10 про setdefault.
Сначала предскажи ответ в голове — это главный навык программиста.
d = {'a': 1, 'b': 2}
for k in d:
print(k, d[k] * 10)
cart = {'чай': 50, 'кофе': 120}
print(sum(cart.values()))
d = {'x': 5}
for k, v in d.items():
print(k, '=', v + 1)
1. Что вернёт вызов stock.values()?
2. По чему идёт цикл for name in prices:, если prices — словарь?
3. Что делает запись for name, price in prices.items():?
4. Как посчитать сумму всех значений словаря prices?
5. Что будет, если добавить пару в словарь прямо внутри цикла for по нему?
6. Чем stock.keys() отличается от list(stock.keys())?
7. Как отобрать только пары со значением меньше 10?
8. Что произойдёт, если внутри цикла for по словарю изменить только значение пары, не добавляя и не удаляя ключи?
Дан словарь prices = {'молоко': 89, 'хлеб': 42, 'сыр': 590}. Перебери пары через items и выведи каждую строкой вида «молоко - 89», затем посчитай общую стоимость через sum по значениям.
Как перебрать словарь в Python?
Циклом for: for name in prices: идёт по ключам, а for name, price in prices.items(): — по парам, где ключ и значение распаковываются в две переменные. Пары идут в порядке вставки.
Урок 5: добавление и изменение парЧто делает метод items() у словаря?
Возвращает представление dict_items со всеми парами «ключ — значение» в виде кортежей. В цикле каждая пара распаковывается в две переменные, а сам словарь при этом не копируется.
Как посчитать сумму значений словаря?
Функцией sum по представлению значений: sum(prices.values()). Число пар даёт len(prices). Для отсутствующих ключей в подсчётах пригодится get с нулём по умолчанию из урока про метод get.
Урок 4: метод getМожно ли изменить словарь во время перебора?
Нет: добавление или удаление пар внутри цикла вызовет RuntimeError: dictionary changed size during iteration. Сначала соберите нужные ключи в отдельный список, а изменения применяйте после цикла.
Что вернёт stock.keys() — список или что-то другое?
Представление dict_keys, а не список. Это «окно» в словарь: если позже добавить пару, в окне она тоже появится. Список с копией данных получается через list(stock.keys()), а в циклах и подсчётах разницы между ними обычно нет.
Как отфильтровать словарь, оставив только дешёвые позиции?
Через dict comprehension: {name: amount for name, amount in stock.items() if amount < 10}. Фигурные скобки строят новый словарь, условие после if отбирает пары. Подробнее о comprehensions — в уроке 15.
Урок 15: dict comprehensionПонравился урок? Сошлитесь на него
«Ключи, значения и пары — три взгляда на один и тот же словарь: данные не копируются, меняется лишь то, что видно из цикла.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Словари · Урок 7
in: проверка ключа в словаре
Оператор in отвечает на вопрос «есть ли такой ключ»: проверка перед доступом вместо KeyError, not in, грабли с проверкой значений и причина скорости поиска.
Словари · Урок 9
Вложенные словари: словарь внутри словаря
Каталог товаров, где у каждого товара свой словарь с ценой и остатком: доступ по цепочке ключей, запись во вложенные поля, перебор и разбор того, почему два уровня вложенности — это предел.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Циклы · Урок 3
Python цикл по строке, словарю и строкам файла
for ходит не только по спискам: строка — последовательность символов, словарь перебирается по ключам, а items отдаёт готовые пары. Плюс построчный перебор текста файла.
python цикл по строкеперебор символов строки python
Циклы · Урок 10
zip в Python: несколько последовательностей параллельно
Два списка идут параллельно: имена и баллы, товары и цены. zip склеивает их в пары, цикл берёт по одной — без индексов и range(len(...)).
python zip циклpython цикл по двум спискам
Словари · Урок 2
Создание словаря: фигурные скобки и dict()
Три способа создать словарь: литерал в фигурных скобках, dict() из списка пар и пустой словарь под наполнение. Плюс разбор, какие ключи годятся и почему fromkeys с пустым списком — ловушка.
dict python созданиесоздание словаря python