Сортировка в Python: частые ошибки и как их избежать
Мутирующий sort() вместо sorted(), сравнение строк с числами, нестабильная логика ключей и падение производительности на больших списках — разбираем ошибки, которые ломают код на проде.

С чего начинаются проблемы
Сортировка в Python выглядит обманчиво простой: есть list.sort(), есть sorted(), оба принимают key и reverse. На этом знания заканчиваются, и начинаются баги: список молча меняется под ногами, сравнение падает с TypeError, а на миллионе строк функция-ключ вызывается миллион раз и всё тормозит. Разбираем конкретные ошибки на Python 3.10+ — поведение sort и sorted в этой части языка стабильно уже много лет, но грабли остаются те же.
Статья про типичные ошибки и как их обойти. Внутренности Timsort и разбор производительности под нагрузкой — отдельная тема, здесь только то, на чём спотыкаются в повседневном коде.
Ошибка 1. Путать sort() и sorted()
Самая частая. list.sort() меняет список на месте и возвращает None. sorted() возвращает новый список и не трогает исходный.
numbers = [3, 1, 2]
# Ошибка: sort() вернул None
result = numbers.sort()
print(result) # None, а не отсортированный список
# Правильно, если нужен новый список:
result = sorted(numbers)
print(result) # [1, 2, 3]
print(numbers) # [3, 1, 2] — не изменился
# Правильно, если хотите изменить исходный:
numbers.sort()
print(numbers) # [1, 2, 3]Строка result = numbers.sort() — классика. Код не падает, просто result становится None, и падение случается на следующей строке, где вы пытаетесь по нему итерироваться. Отладка уходит не туда.
Правило простое:sort()— глагол, действие над списком, ничего не возвращает.sorted()— существительное, отдаёт результат. Если пишете присваивание — вам почти всегда нуженsorted().
Ошибка 2. Сравнивать несравнимое
В Python 3 сравнение разных типов запрещено. Список с числами и строками отсортировать не выйдет.
mixed = [3, '1', 2]
sorted(mixed)
# TypeError: '<' not supported between instances of 'str' and 'int'То же самое случается со списком словарей, где по нужному ключу иногда лежит None:
users = [
{'name': 'Anna', 'age': 30},
{'name': 'Boris', 'age': None},
]
sorted(users, key=lambda u: u['age'])
# TypeError: '<' not supported between instances of 'NoneType' and 'int'Решение — привести данные к сравнимому виду в самом ключе. Например, отправить None в конец, подставив условное большое значение или используя кортеж-флаг:
# None уходит в конец: сначала сравниваем флаг (0/1), потом возраст
sorted(users, key=lambda u: (u['age'] is None, u['age'] or 0))Кортеж в ключе сравнивается поэлементно: сначала False (0) против True (1), и только при равенстве — второй элемент. Это же приём для многоуровневой сортировки.
Ошибка 3. Использовать cmp вместо key
В Python 2 был параметр cmp, принимавший функцию сравнения двух элементов. В Python 3 его нет. Старые ответы в интернете предлагают sorted(data, cmp=my_compare) — это выбросит TypeError: 'cmp' is an unexpected keyword argument.
Если логика сравнения действительно нельзя выразить через ключ (например, порядок зависит от пары элементов), используйте functools.cmp_to_key:
from functools import cmp_to_key
def compare(a, b):
# вернуть отрицательное, если a должен идти раньше b
return a['priority'] - b['priority']
items = [{'priority': 2}, {'priority': 1}]
result = sorted(items, key=cmp_to_key(compare))Но в 95% случаев key проще, быстрее и читаемее. cmp_to_key вызывает функцию сравнения многократно и медленнее ключа. Тянитесь к нему только когда порядок реально не сводится к одному вычислимому значению.
Ошибка 4. Считать, что несколько sort() дадут нужный порядок
Сортировка в Python стабильная: элементы с равными ключами сохраняют исходный взаимный порядок. Это можно использовать для многоуровневой сортировки, но новички делают наоборот.
Чтобы отсортировать по возрасту, а при равном возрасте — по имени, есть два пути. Правильный — один составной ключ:
sorted(users, key=lambda u: (u['age'], u['name']))Второй путь опирается на стабильность: сортировать по менее значимому полю первым, потом по более значимому.
# сначала по имени (вторичный ключ), потом по возрасту (первичный)
users.sort(key=lambda u: u['name'])
users.sort(key=lambda u: u['age'])Порядок вызовов здесь обратный интуиции. Если перепутать и первым отсортировать по возрасту, второй проход по имени всё перемешает. Составной ключ ошибиться не даёт — предпочитайте его.
Ошибка 5. Дорогой ключ, который считается на каждый элемент
key вызывается ровно один раз на элемент, результаты кешируются — это хорошо. Плохо, когда сама функция ключа делает лишнюю работу.
# Плохо: .lower() и strip() гоняются в лямбде,
# но проблема в другом — обращение к БД или сети внутри ключа
sorted(records, key=lambda r: fetch_score(r.id)) # запрос на каждый элементЕсли fetch_score ходит в базу, вы получите N запросов на этапе построения ключей. Вынесите тяжёлую подготовку заранее, а в ключ подавайте уже готовые значения.
# Хорошо: посчитали один раз, разложили в словарь
scores = load_all_scores([r.id for r in records])
sorted(records, key=lambda r: scores[r.id])Мелкая, но частая: reverse и стабильность
reverse=True сохраняет стабильность — равные элементы не переворачиваются между собой, переворачивается общий порядок. Не путайте с sorted(...)[::-1], который переворачивает и равные элементы тоже. Если для вас важен порядок при равных ключах, используйте reverse=True, а не срез.
| Способ | Что делает | Стабильность при равных |
|---|---|---|
sorted(x, reverse=True) | обратный порядок | сохраняется |
sorted(x)[::-1] | переворачивает результат | ломается |
Ошибка 6. Сортировать словарь и удивляться
sorted(my_dict) вернёт список ключей, а не пар и не значения. Чтобы получить упорядоченные пары, сортируйте .items():
prices = {'banana': 3, 'apple': 1, 'cherry': 2}
# по ключу
sorted(prices.items())
# по значению
sorted(prices.items(), key=lambda kv: kv[1])Результат — список кортежей. Если нужен снова словарь, оберните в dict(...): с Python 3.7 порядок вставки в словарях гарантирован, так что отсортированный порядок сохранится.
Что проверить перед коммитом
- Присваиваете результат — используете
sorted(), а не.sort(). - В данных могут быть
Noneили смешанные типы — ключ приводит их к сравнимому виду. - Многоуровневая сортировка — один составной ключ-кортеж, а не цепочка
.sort(). - Ключ не делает запросов в БД, файлы или сеть — тяжёлое посчитано заранее.
- Нужен обратный порядок с сохранением стабильности —
reverse=True, не срез[::-1].
Что дальше
Если сортируете большие потоки и вам нужны только N наибольших или наименьших элементов — не сортируйте весь список. Смотрите heapq.nlargest и heapq.nsmallest: они работают за меньшую сложность и память. Если сортируете по локали (буквы с диакритикой, кириллица вперемешку с латиницей) — разбирайтесь с модулем locale или библиотекой для естественной сортировки, потому что обычный порядок кодовых точек даст неожиданный результат.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: Python HOWTO: Sorting Techniques (официальная документация), Python docs: list.sort / sorted
Частые вопросы
Чем sort() отличается от sorted() простыми словами?
Почему у меня TypeError при сортировке списка словарей?
У меня Python 2, всё это работает?
Как отсортировать сразу по двум полям?
Сортировка испортит порядок одинаковых элементов?
Что делать, если сортировка тормозит на большом списке?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.