Как отсортировать словарь в Python

Начиная с Python 3.7 словарь гарантированно сохраняет порядок вставки ключей, поэтому "отсортированный словарь" стал вполне реальной вещью: достаточно собрать новый словарь из пар в нужном порядке. При этом у самого dict нет метода sort(), и вся работа идет через встроенную функцию sorted. Ниже разобраны сортировка по ключу и по значению, по убыванию, по нескольким критериям, сортировка вложенных словарей и списков словарей, а также быстрый способ получить топ значений через Counter.
Как устроена сортировка словаря
Функция sorted(iterable, key=None, reverse=False) принимает любой итерируемый объект и возвращает новый список. Словарь при итерации отдает ключи, поэтому sorted(d) вернет отсортированный список ключей, а не словарь. Чтобы получить именно словарь, сортируют пары d.items() и передают результат в конструктор dict.
prices = {"banana": 89, "apple": 120, "cherry": 450, "kiwi": 35}
print(sorted(prices)) # -> ['apple', 'banana', 'cherry', 'kiwi']
print(sorted(prices.items()))
# -> [('apple', 120), ('banana', 89), ('cherry', 450), ('kiwi', 35)]
by_key = dict(sorted(prices.items()))
print(by_key)
# -> {'apple': 120, 'banana': 89, 'cherry': 450, 'kiwi': 35}
Исходный словарь при этом не меняется. Если нужно "отсортировать на месте", просто переприсвойте переменную или обновите содержимое: prices = dict(sorted(prices.items())). Про устройство словарей и их методы подробно рассказано в статье Словари в Python.
Сортировка словаря по ключу
Кортежи сравниваются поэлементно: сначала первые элементы, при равенстве вторые. Ключи словаря уникальны, поэтому sorted(d.items()) сортирует пары ровно по ключу, до значений дело не доходит. Это самый короткий способ отсортировать словарь по ключам.
grades = {"Петров": 4, "Алексеева": 5, "Смирнов": 3, "Борисов": 5}
print(dict(sorted(grades.items())))
# -> {'Алексеева': 5, 'Борисов': 5, 'Петров': 4, 'Смирнов': 3}
# по убыванию ключей
print(dict(sorted(grades.items(), reverse=True)))
# -> {'Смирнов': 3, 'Петров': 4, 'Борисов': 5, 'Алексеева': 5}
Ключи в разном регистре и числа-строки
Строки сравниваются по кодам символов, поэтому заглавные буквы идут раньше строчных, а строка "10" оказывается раньше "9". Для таких случаев нужен параметр key, который преобразует элемент перед сравнением.
d = {"beta": 2, "Alpha": 1, "gamma": 3, "Delta": 4}
print(dict(sorted(d.items())))
# -> {'Alpha': 1, 'Delta': 4, 'beta': 2, 'gamma': 3}
print(dict(sorted(d.items(), key=lambda kv: kv[0].lower())))
# -> {'Alpha': 1, 'beta': 2, 'Delta': 4, 'gamma': 3}
pages = {"10": "Итоги", "2": "Введение", "1": "Обложка"}
print(dict(sorted(pages.items(), key=lambda kv: int(kv[0]))))
# -> {'1': 'Обложка', '2': 'Введение', '10': 'Итоги'}
Буква "ё" в кодировке Unicode стоит после "я", поэтому простая сортировка русских слов с "ё" дает неожиданный порядок. Если это важно, подмените ее в ключе: key=lambda kv: kv[0].lower().replace("ё", "е").
Сортировка словаря по значению
Самый частый запрос. Параметр key получает каждую пару (ключ, значение) и должен вернуть то, по чему сортировать. Для значения это второй элемент кортежа, kv[1].
prices = {"banana": 89, "apple": 120, "cherry": 450, "kiwi": 35}
cheap_first = dict(sorted(prices.items(), key=lambda kv: kv[1]))
print(cheap_first)
# -> {'kiwi': 35, 'banana': 89, 'apple': 120, 'cherry': 450}
expensive_first = dict(sorted(prices.items(), key=lambda kv: kv[1], reverse=True))
print(expensive_first)
# -> {'cherry': 450, 'apple': 120, 'banana': 89, 'kiwi': 35}
Если нужны только ключи в порядке значений, можно сортировать сам словарь, передав в качестве key метод get: sorted(prices, key=prices.get) вернет ['kiwi', 'banana', 'apple', 'cherry']. Запись короткая и не создает промежуточных кортежей. Подробнее о синтаксисе lambda в статье Лямбда функция в Python.
operator.itemgetter вместо lambda
Модуль operator предлагает готовую функцию itemgetter(i), которая делает то же, что lambda x: x[i]. Она реализована на C, работает немного быстрее и, по мнению многих, читается чище. С несколькими аргументами она возвращает кортеж, что удобно для сортировки по нескольким полям.
from operator import itemgetter
prices = {"banana": 89, "apple": 120, "cherry": 450, "kiwi": 35}
print(dict(sorted(prices.items(), key=itemgetter(1))))
# -> {'kiwi': 35, 'banana': 89, 'apple': 120, 'cherry': 450}
get_name_age = itemgetter("name", "age")
print(get_name_age({"name": "Анна", "age": 31, "city": "Казань"}))
# -> ('Анна', 31)
Сортировка по нескольким критериям
Чтобы сортировать по значению, а при равных значениях по ключу, верните из key кортеж. Python сравнит кортежи поэлементно.
scores = {"Олег": 87, "Анна": 95, "Ирина": 87, "Борис": 95, "Вера": 70}
ranked = sorted(scores.items(), key=lambda kv: (-kv[1], kv[0]))
for name, score in ranked:
print(f"{score:>3} {name}")
# -> 95 Анна
# -> 95 Борис
# -> 87 Ирина
# -> 87 Олег
# -> 70 Вера
Минус перед числом дает убывание по баллам, а имена при этом идут по возрастанию. Трюк работает только для чисел. Если нужно сортировать по убыванию строк и по возрастанию чисел, используйте устойчивость сортировки: Python гарантирует, что элементы с равным ключом сохраняют взаимный порядок. Значит, можно отсортировать дважды, начиная со второстепенного критерия.
items = list(scores.items())
items.sort(key=itemgetter(0), reverse=True) # второстепенный: имя по убыванию
items.sort(key=itemgetter(1)) # главный: балл по возрастанию
print(items)
# -> [('Вера', 70), ('Олег', 87), ('Ирина', 87), ('Борис', 95), ('Анна', 95)]
Разница между list.sort() и sorted(), а также подробности про устойчивость сортировки описаны в статье Python List sort.
Сортировка значений внутри словаря
Иногда нужно упорядочить не пары словаря, а сами значения, когда это списки. Например, в словаре хранятся оценки каждого студента, и для отчета их нужно вывести по возрастанию. Здесь порядок ключей не меняется, а каждый список сортируется отдельно. Если исходные данные нельзя трогать, создайте новый словарь через генератор словаря, если можно, отсортируйте списки на месте методом sort.
marks = {"Анна": [5, 3, 4], "Борис": [4, 4, 2], "Вера": [3, 5, 5]}
sorted_marks = {name: sorted(values) for name, values in marks.items()}
print(sorted_marks)
# -> {'Анна': [3, 4, 5], 'Борис': [2, 4, 4], 'Вера': [3, 5, 5]}
for values in marks.values():
values.sort(reverse=True)
print(marks)
# -> {'Анна': [5, 4, 3], 'Борис': [4, 4, 2], 'Вера': [5, 5, 3]}
Обе операции можно совместить: отсортировать студентов по среднему баллу и вывести каждому отсортированный список. В качестве ключа сортировки тогда выступает выражение, вычисленное по значению, например sum(v) / len(v). Ключевая функция вызывается ровно один раз для каждого элемента, поэтому даже дорогие вычисления в ней не умножаются на количество сравнений.
from statistics import mean
marks = {"Анна": [5, 3, 4], "Борис": [4, 4, 2], "Вера": [3, 5, 5]}
for name, values in sorted(marks.items(), key=lambda kv: mean(kv[1]), reverse=True):
print(f"{name}: {mean(values):.2f}")
# -> Вера: 4.33
# -> Анна: 4.00
# -> Борис: 3.33
Сортировка по датам и по длине
Даты в строковом виде сортируются корректно только в формате ISO ГГГГ-ММ-ДД, где старшие разряды стоят слева. Формат ДД.ММ.ГГГГ при обычной строковой сортировке перемешает годы, поэтому строку нужно разобрать в объект date прямо в ключе.
from datetime import datetime
deadlines = {"отчет": "15.11.2026", "релиз": "03.12.2025", "аудит": "01.02.2026"}
by_date = dict(sorted(
deadlines.items(),
key=lambda kv: datetime.strptime(kv[1], "%d.%m.%Y"),
))
print(by_date)
# -> {'релиз': '03.12.2025', 'аудит': '01.02.2026', 'отчет': '15.11.2026'}
words = {"кот": 3, "гиппопотам": 1, "слон": 7}
print(sorted(words, key=len)) # -> ['кот', 'слон', 'гиппопотам']
В последней строке ключом служит встроенная функция len: в key можно передать любую функцию одного аргумента, не только lambda. Так же работают str.lower, abs или собственная функция с понятным именем, которая к тому же легко тестируется отдельно.
Шпаргалка по способам
| Задача | Код |
|---|---|
| Словарь по ключам | dict(sorted(d.items())) |
| Словарь по ключам, убывание | dict(sorted(d.items(), reverse=True)) |
| Словарь по значениям | dict(sorted(d.items(), key=itemgetter(1))) |
| Словарь по значениям, убывание | dict(sorted(d.items(), key=itemgetter(1), reverse=True)) |
| Список ключей по значениям | sorted(d, key=d.get) |
| Топ-3 по значению | heapq.nlargest(3, d.items(), key=itemgetter(1)) |
| Ключ с максимальным значением | max(d, key=d.get) |
| Частоты, самые частые первыми | Counter(data).most_common() |
Вложенные словари и списки словарей
В реальных данных значения редко бывают простыми числами. Чаще это вложенный словарь с полями, например результат разбора JSON. Принцип тот же: в key вы достаете нужное поле.
users = {
"u1": {"name": "Анна", "age": 31, "orders": 5},
"u2": {"name": "Борис", "age": 25, "orders": 12},
"u3": {"name": "Вера", "age": 42, "orders": 5},
}
by_orders = dict(sorted(users.items(), key=lambda kv: kv[1]["orders"], reverse=True))
print(list(by_orders)) # -> ['u2', 'u1', 'u3']
by_orders_then_age = sorted(
users.items(),
key=lambda kv: (-kv[1]["orders"], kv[1]["age"]),
)
print([uid for uid, _ in by_orders_then_age]) # -> ['u2', 'u1', 'u3']
Сортировка списка словарей
Список словарей сортируется проще, потому что каждый элемент уже является словарем, и itemgetter принимает имя поля напрямую.
from operator import itemgetter
rows = [
{"city": "Казань", "temp": 12},
{"city": "Сочи", "temp": 19},
{"city": "Мурманск", "temp": 2},
]
rows.sort(key=itemgetter("temp"), reverse=True)
print([r["city"] for r in rows]) # -> ['Сочи', 'Казань', 'Мурманск']
Если поле может отсутствовать или быть None
Когда у части записей нет поля или оно равно None, сравнение упадет с TypeError: '<' not supported between instances of 'NoneType' and 'int'. Решение: в ключе вернуть кортеж, где первый элемент отправляет пустые значения в конец.
rows = [{"n": "a", "v": 3}, {"n": "b"}, {"n": "c", "v": None}, {"n": "d", "v": 1}]
def by_v(row):
v = row.get("v")
return (v is None, v if v is not None else 0)
print([r["n"] for r in sorted(rows, key=by_v)]) # -> ['d', 'a', 'b', 'c']
False меньше True, поэтому записи с заполненным полем идут первыми, а пустые в конце в исходном порядке.
Counter.most_common: топ значений одной строкой
Частая задача: посчитать, сколько раз встречается каждое слово или каждый статус, и вывести самые частые. Для нее есть collections.Counter. Это подкласс словаря, а его метод most_common(n) возвращает список пар, отсортированный по убыванию частоты.
from collections import Counter
statuses = ["200", "404", "200", "500", "200", "404", "301"]
c = Counter(statuses)
print(c) # -> Counter({'200': 3, '404': 2, '500': 1, '301': 1})
print(c.most_common(2)) # -> [('200', 3), ('404', 2)]
print(c.most_common()[-1]) # -> ('301', 1) (самый редкий)
text = "мама мыла раму мама мыла пол"
print(Counter(text.split()).most_common(1)) # -> [('мама', 2)]
При равной частоте элементы идут в порядке первого появления. Если у вас уже есть обычный словарь с количествами, оберните его: Counter(d).most_common(5). Внутри для небольшого n используется heapq.nlargest, что быстрее полной сортировки на больших данных. Остальные структуры модуля разобраны в статье модуль collections в Python.
Практический пример: отчет по продажам
Соберем приемы в одну небольшую задачу. Есть список продаж за день, каждая продажа - это пара "товар, сумма". Нужно посчитать выручку по каждому товару, вывести товары от самого прибыльного к наименее прибыльному, а при равной выручке упорядочить их по названию. Отдельно нужно показать три самых популярных товара по количеству продаж.
from collections import Counter, defaultdict
sales = [
("чай", 250), ("кофе", 320), ("чай", 250), ("сок", 180),
("кофе", 320), ("вода", 90), ("сок", 320), ("вода", 90),
]
revenue = defaultdict(int)
for product, amount in sales:
revenue[product] += amount
report = sorted(revenue.items(), key=lambda kv: (-kv[1], kv[0]))
for product, total in report:
print(f"{product:<6}{total:>6}")
# -> кофе 640
# -> сок 500
# -> чай 500
# -> вода 180
popular = Counter(product for product, _ in sales).most_common(3)
print(popular) # -> [('чай', 2), ('кофе', 2), ('сок', 2)]
Обратите внимание на несколько деталей. Для накопления сумм использован defaultdict(int), который избавляет от проверки наличия ключа. Сортировка выполняется один раз в конце, а не после каждого добавления, потому что промежуточный порядок никому не нужен. Сок и чай набрали одинаковую выручку, и вторичный критерий по названию сделал порядок предсказуемым: без него результат зависел бы от порядка, в котором товары впервые встретились в данных. В выдаче most_common при равной частоте сохраняется порядок первого появления, поэтому "вода" с теми же двумя продажами не попала в тройку. Если такое поведение не устраивает, сортируйте пары из Counter сами с нужным вторичным ключом.
Производительность и когда сортировать не нужно
Сортировка занимает O(n log n). Для словаря на сотни тысяч записей это доли секунды, но в цикле может стать узким местом. Несколько практических правил:
- Нужен только максимум или минимум - используйте
max(d, key=d.get), это O(n). - Нужен топ-k при большом n - используйте
heapq.nlargest(k, ...)илиCounter.most_common(k). - Порядок нужен только для вывода - сортируйте прямо в цикле
for k, v in sorted(...), не создавая промежуточный словарь. - Данные постоянно добавляются и должны оставаться упорядоченными - рассмотрите сторонний пакет
sortedcontainersили вставку в список через модульbisect.
import heapq
from operator import itemgetter
sales = {f"item{i}": (i * 7919) % 1000 for i in range(100_000)}
top3 = heapq.nlargest(3, sales.items(), key=itemgetter(1))
print(top3)
Частые ошибки
- Ожидание, что
sorted(d)вернет словарь. Он возвращает список ключей. - Вызов
d.sort(). У словаря нет такого метода, будетAttributeError. - Использование
key=lambda x: x[1]при сортировке самого словаря, а не егоitems(). В этом случаеxбудет ключом, иx[1]вернет второй символ строки. - Сортировка значений разных типов, например чисел и строк вперемешку. В Python 3 это вызывает
TypeError, приведите значения к одному типу в ключе. - Использование
OrderedDictтам, где хватает обычногоdict. С Python 3.7 он нужен только для специфичных операций вродеmove_to_end.
Частые вопросы
Сохранит ли словарь порядок после сортировки?
Да. Словарь, созданный через dict(sorted(...)), хранит ключи в том порядке, в котором их вставили. Но если вы потом добавите новый ключ, он окажется в конце, и порядок придется восстанавливать повторной сортировкой.
Как отсортировать словарь по значению по убыванию?
dict(sorted(d.items(), key=lambda kv: kv[1], reverse=True)). Для чисел можно также писать key=lambda kv: -kv[1], результат будет тем же, но вариант с reverse понятнее и работает для любых типов.
Что лучше: lambda или itemgetter?
По результату они одинаковы. itemgetter немного быстрее и удобен для выборки нескольких полей, lambda гибче, когда нужно преобразование: регистр, приведение к числу, обработка None. Выбирайте то, что читается лучше в конкретном месте.
Как получить первые N элементов отсортированного словаря?
Срежьте список пар перед созданием словаря: dict(sorted(d.items(), key=itemgetter(1), reverse=True)[:5]). Для больших словарей быстрее dict(heapq.nlargest(5, d.items(), key=itemgetter(1))).
Как отсортировать ключи при сохранении в JSON?
Сортировать словарь заранее не нужно: передайте sort_keys=True в json.dumps или json.dump, и ключи будут упорядочены на всех уровнях вложенности.
Как отсортировать словарь, если ключи разных типов?
Прямое сравнение числа и строки в Python 3 запрещено, поэтому sorted(d.items()) упадет с TypeError. Задайте ключ, который сначала группирует по типу, а потом сравнивает значения внутри группы: key=lambda kv: (isinstance(kv[0], str), str(kv[0])). Числовые ключи окажутся первыми, строковые после них. Если ключи на самом деле должны быть одного типа, лучше исправить данные при загрузке, чем маскировать проблему в сортировке.