Словарь в Python: обход, сортировка и фильтрация без лишних копий
Разбираем, как правильно перебирать, сортировать и отсеивать элементы словаря в CPython 3.11–3.13: чем items() отличается от keys(), почему sorted() не меняет исходный dict и где генератор словаря выигрывает у цикла.

Словарь — самая используемая структура данных в Python после списка, и почти каждая задача над ним сводится к трём операциям: обойти, отсортировать, отфильтровать. Ниже — рабочие приёмы для CPython 3.11–3.13 с разбором цены каждого: где создаётся лишняя копия, где вы получаете представление (view), а где — новый объект. Очевидное решение вроде for k in d.keys() работает, но часто делает не то, что вы думаете, и стоит дороже нужного.
Обход: keys(), values(), items() и что они возвращают
Три метода словаря возвращают не списки, а view-объекты — динамические представления, которые отражают текущее содержимое словаря и не копируют данные. Это ключевое отличие от Python 2, где d.keys() возвращал список.
View (представление) — объект, который ссылается на внутренние данные словаря. Он не хранит копию: если словарь изменится, изменится и то, что видно через view. Занимает константную память независимо от размера словаря.
# Python 3.11+
prices = {"apple": 50, "banana": 30, "cherry": 90}
# Обход по ключам — так делает и просто for x in prices
for key in prices:
print(key)
# Обход по значениям
for value in prices.values():
print(value)
# Обход по парам — самый частый случай
for key, value in prices.items():
print(key, value)
Простой for key in prices эквивалентен for key in prices.keys() — метод вызывать не обязательно. А вот распаковка for key, value in prices.items() избавляет от повторных обращений prices[key] внутри тела цикла, и это не только короче, но и быстрее: доступ по ключу — это хеширование и поиск в таблице на каждой итерации.
Порядок обхода
Начиная с CPython 3.7 порядок вставки гарантирован спецификацией языка, а не деталью реализации. Обход даёт элементы в том порядке, в котором ключи добавлялись. Полагаться на это можно — но не путайте порядок вставки с отсортированным порядком, это разные вещи.
Изменение словаря во время обхода
Нельзя добавлять или удалять ключи, пока вы итерируете по словарю или его view. CPython бросит RuntimeError: dictionary changed size during iteration. Меняйте только значения существующих ключей, либо собирайте изменения отдельно.
# Так нельзя — RuntimeError
# for key in prices:
# if prices[key] < 40:
# del prices[key]
# Так можно — фиксируем ключи в список заранее
for key in list(prices.keys()):
if prices[key] < 40:
del prices[key]
Сортировка: sorted() не меняет словарь
У словаря нет метода .sort() — в отличие от списка. Отсортировать «на месте» словарь нельзя концептуально: сортировка возвращает новый порядок, и вы либо строите новый словарь, либо получаете отсортированный список пар. Функция sorted() всегда возвращает список и никогда не трогает исходный объект.
По ключам
prices = {"cherry": 90, "apple": 50, "banana": 30}
# Отсортированный новый словарь по ключам
by_key = dict(sorted(prices.items()))
# {'apple': 50, 'banana': 30, 'cherry': 90}
По умолчанию sorted(prices.items()) сортирует кортежи (ключ, значение) сначала по первому элементу — то есть по ключу. Результат dict(...) сохранит этот порядок, потому что dict запоминает порядок вставки.
По значениям
from operator import itemgetter
# По значению, по возрастанию
by_value = dict(sorted(prices.items(), key=itemgetter(1)))
# {'banana': 30, 'apple': 50, 'cherry': 90}
# По значению, по убыванию
by_value_desc = dict(sorted(prices.items(), key=itemgetter(1), reverse=True))
itemgetter(1) из модуля operator берёт второй элемент кортежа. Можно написать и key=lambda item: item[1] — результат тот же, но itemgetter реализован на C и на больших словарях заметно быстрее лямбды.
Составной ключ сортировки
# Сначала по значению по убыванию, при равных — по ключу по алфавиту
result = dict(sorted(
prices.items(),
key=lambda item: (-item[1], item[0])
))
Отрицание -item[1] разворачивает порядок только для числового поля, оставляя ключ по возрастанию. Это чище, чем два вызова sorted подряд, и работает за один проход.
Фильтрация: генератор словаря против цикла
Отфильтровать словарь — значит собрать новый из тех пар, что прошли условие. Три подхода, и у каждого своя цена.
Подход 1 — генератор словаря (dict comprehension)
prices = {"apple": 50, "banana": 30, "cherry": 90}
# Только товары дороже 40
expensive = {k: v for k, v in prices.items() if v > 40}
# {'apple': 50, 'cherry': 90}
Читается как условие, работает быстро, создаёт новый словарь. Для большинства случаев это дефолтный выбор.
Подход 2 — filter() + dict()
# filter возвращает ленивый итератор, dict материализует результат
expensive = dict(filter(lambda item: item[1] > 40, prices.items()))
Функционально то же самое, но лямбда медленнее генератора, а читаемость хуже. Оправдано, только если функция-предикат уже есть готовая и её не нужно оборачивать в лямбду.
Подход 3 — модификация на месте через список ключей
# Удаляем неподходящие прямо из исходного словаря
for key in list(prices):
if prices[key] <= 40:
del prices[key]
Единственный способ, который не создаёт новый словарь, — но требует list() вокруг ключей, иначе RuntimeError. Мутирует исходные данные, что опасно, если на словарь ссылается кто-то ещё.
Когда что выбирать
| Задача | Приём | Цена |
|---|---|---|
| Обойти пары | for k, v in d.items() | Нет копии, view динамический |
| Удалить во время обхода | for k in list(d) | Копия ключей в памяти |
| Отсортировать по значению | dict(sorted(d.items(), key=itemgetter(1))) | Новый список + новый dict |
| Отфильтровать, исходник нужен | dict comprehension | Новый dict, исходный цел |
| Отфильтровать, исходник не нужен | del в цикле по list(d) | Мутация, копия ключей |
Главное различие в цене — создаёте вы новый объект или получаете представление. items(), keys(), values() ничего не копируют; sorted() и генератор словаря копируют всегда. На словаре в десять элементов это не важно, на миллионе — решает.
Что ломается
- RuntimeError при удалении в цикле. Забыли обернуть ключи в
list()— итерация по изменяемому словарю падает. Всегда фиксируйте ключи заранее, если планируетеdel. - Сортировка по значению падает на смешанных типах. Если значения — числа и строки вперемешку,
sortedброситTypeError: '<' not supported. Приводите к одному типу в key-функции или фильтруйте до сортировки. - Мутация чужого словаря. Модификация на месте меняет объект для всех, кто на него ссылается. Если словарь пришёл аргументом функции — фильтруйте в новый, не трогая вход.
- Порядок теряется при dict(sorted(...)) в старых версиях. На CPython до 3.7 порядок вставки не гарантирован, и обёртка в
dictсортировку «терялась». В 3.7+ проблемы нет.
Что делать дальше
Если сортируете один и тот же словарь по разным полям многократно — не сортируйте dict, держите данные в списке словарей или используйте collections.OrderedDict только там, где нужны его специфичные методы (move_to_end). Для частых выборок по значению словарь — неподходящая структура: он индексирован по ключу, и любая фильтрация по значению линейна. Если такие запросы горячие, смотрите в сторону sqlite3 в памяти или отдельного индекса. Замерять стоит через timeit на реальном объёме данных, а не на трёх фруктах из примеров.
Генератор изображений: Иллюстрация, обложка, баннер, схема — по текстовому описанию, без исходного фото.
Спросить за 20 ₽Источники: Python docs — Mapping Types: dict, Python docs — sorted() and operator.itemgetter
Частые вопросы
В чём разница между d.keys() и list(d.keys())?
Можно ли отсортировать словарь на месте, как список методом .sort()?
У меня Python 3.6 — гарантирован ли порядок обхода?
Почему при удалении элементов в цикле падает RuntimeError?
Что быстрее для фильтрации — dict comprehension или filter()?
Как отсортировать по значению, а при равных значениях — по ключу?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.