Сортировка по нескольким ключам в Python: пошаговая инструкция
За 15 минут разберётесь, как сортировать список словарей и кортежей сразу по двум-трём полям — например, сначала по фамилии, потом по возрасту, где-то по возрастанию, где-то по убыванию.

Задача звучит просто: есть список сотрудников, нужно отсортировать сначала по отделу, внутри отдела — по зарплате от большей к меньшей, а при равной зарплате — по фамилии. Через пятнадцать минут вы будете уметь делать это одной строкой. Всё рабочее для Python 3.8 и новее, ничего устанавливать не нужно — только встроенные sorted(), list.sort() и модуль operator из стандартной библиотеки.
Что нужно иметь до начала
- Python 3.8 или новее (проверьте командой
python --versionв терминале). - Любой редактор или интерактивная оболочка — подойдёт даже
pythonбез файла. - Понимание, что такое список, кортеж и словарь. Модель памяти и алгоритмы сортировки знать не нужно.
Одно предложение теории, которое пригодится дальше: сортировка в Python стабильна — элементы с одинаковым ключом сохраняют исходный порядок. На этом свойстве держится половина приёмов ниже.
Шаг 1. Сортировка по одному ключу
Начнём с простого. Пусть есть список словарей:
people = [
{"name": "Иванов", "dept": "IT", "salary": 120000},
{"name": "Петров", "dept": "HR", "salary": 90000},
{"name": "Сидоров", "dept": "IT", "salary": 120000},
{"name": "Абрамов", "dept": "HR", "salary": 90000},
]
# Сортируем по зарплате по возрастанию
result = sorted(people, key=lambda person: person["salary"])
for row in result:
print(row["name"], row["salary"])
Проверка: в выводе первыми должны идти два человека с зарплатой 90000, затем два со 120000. Порядок Петров, Абрамов, Иванов, Сидоров — тот же, что во входном списке, потому что сортировка стабильна.
Функция sorted() возвращает новый список и не трогает исходный. Если хотите изменить список на месте — используйте people.sort(key=...) с теми же аргументами.
Шаг 2. Два ключа в одном направлении
Самый чистый способ сортировать по нескольким полям, когда все они идут в одну сторону, — вернуть из key кортеж. Python сравнивает кортежи поэлементно: сначала первый элемент, при равенстве — второй, и так далее.
result = sorted(people, key=lambda p: (p["dept"], p["salary"]))
for row in result:
print(row["dept"], row["salary"], row["name"])
Проверка: сначала идут все из HR (по зарплате по возрастанию), затем все из IT. Внутри одинакового отдела и одинаковой зарплаты сохраняется исходный порядок.
Порядок полей в кортеже — это порядок приоритета. Первый элемент важнее, последний решает только при полном совпадении предыдущих.
Шаг 3. Разные направления для разных ключей
Здесь начинается настоящая задача: отдел по возрастанию, а зарплата — по убыванию. Флаг reverse=True не подойдёт, потому что он переворачивает всю сортировку сразу, а не отдельный ключ.
Приём для чисел: минус перед значением
Если ключ, который нужно развернуть, числовой, поставьте перед ним минус:
result = sorted(people, key=lambda p: (p["dept"], -p["salary"]))
for row in result:
print(row["dept"], row["salary"], row["name"])
Проверка: отделы идут по алфавиту (HR, потом IT), а внутри каждого — сначала большая зарплата, потом меньшая.
Приём для любых типов: последовательные сортировки
Минус работает только с числами — со строками так не выйдет. Здесь спасает стабильность сортировки: сортируйте от менее важного ключа к более важному, каждый раз применяя reverse по отдельности.
# Сначала сортируем по вторичному ключу — зарплата по убыванию
people.sort(key=lambda p: p["salary"], reverse=True)
# Затем по главному ключу — отдел по возрастанию
people.sort(key=lambda p: p["dept"])
for row in people:
print(row["dept"], row["salary"], row["name"])
Порядок действий кажется обратным здравому смыслу, но он верный: последняя сортировка задаёт главный порядок, а стабильность сохраняет результат предыдущих внутри равных групп. Проверка: результат совпадает с приёмом через минус.
Сортируйте по ключам в обратном порядке приоритета: самый важный ключ — последней командой sort. Это единственный универсальный способ смешивать возрастание и убывание для строк, дат и чисел одновременно.
Шаг 4. operator.itemgetter вместо lambda
Когда ключи идут в одном направлении, itemgetter из модуля operator читается чище и работает чуть быстрее lambda, потому что вызов уходит в С-код:
from operator import itemgetter
# Эквивалент sorted(people, key=lambda p: (p["dept"], p["salary"]))
result = sorted(people, key=itemgetter("dept", "salary"))
for row in result:
print(row["dept"], row["salary"], row["name"])
Ограничение: itemgetter не умеет разворачивать отдельный ключ. Как только вам нужны разные направления, возвращайтесь к минусу или к последовательным сортировкам из шага 3.
Сравнение способов
| Способ | Разные направления | Когда брать |
|---|---|---|
| Кортеж в lambda | Только через минус (числа) | Несколько ключей в одну сторону |
| itemgetter | Нет | Все ключи по возрастанию, нужна скорость и читаемость |
| Минус перед значением | Да, для чисел | Числовой ключ надо развернуть |
| Последовательные sort | Да, для любых типов | Строки/даты вперемешку с разными направлениями |
Если не получилось
- TypeError: '<' not supported between instances of 'NoneType' and 'str' — в одном из полей затесался
None. Python не умеет сравниватьNoneсо строкой или числом. Обработайте пропуски в ключе, например:key=lambda p: (p.get("dept") or "", p["salary"]). - KeyError: 'salary' — в каком-то словаре нет такого ключа. Замените
p["salary"]наp.get("salary", 0), чтобы отсутствующие значения не роняли сортировку. - Минус не работает со строками — конструкция
-p["name"]даётTypeError: bad operand type for unary -: 'str'. Для строк применяйте последовательные сортировки из шага 3. - Изменения не сохранились — вы вызвали
sorted(people), но не присвоили результат.sorted()возвращает новый список; либо присвойте его переменной, либо используйтеpeople.sort(). - Регистр ломает порядок строк — «Яблоко» встаёт перед «абрикос», потому что заглавные буквы в Юникоде идут раньше строчных. Приводите ключ к одному регистру:
key=lambda p: p["name"].casefold().
Что дальше
Если сортируете большие списки в цикле, вынесите itemgetter в переменную один раз и переиспользуйте — так вы не пересоздаёте функцию на каждой итерации. Для сортировки по вычисляемому ключу (например, по длине строки или по дате из текстового поля) считайте значение внутри lambda один раз через кортеж, а не дёргайте одно и то же выражение дважды. Инвалидация и кеширование ключей через functools — отдельная тема, здесь мы её не трогаем.
Антиблок-аналитик 115-ФЗ: Анализ выписок, поиск рисков блокировки счёта, инструкции по безопасным транзакциям.
Спросить за 41 ₽Источники: Python HOWTO: Sorting Techniques, operator — Standard operators as functions
Частые вопросы
А если у меня Python 3.6 или 3.7?
Чем sorted() отличается от list.sort()?
Как развернуть строковый ключ без последовательных сортировок?
Почему при последовательных сортировках главный ключ идёт последним?
itemgetter быстрее lambda — насколько это важно?
Что делать, если в поле встречается None?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.