Есть баг, который хоть раз ловит почти каждый, кто учит Python. Пишешь numbers = numbers.sort(), запускаешь — и в numbers лежит None. Список вроде бы отсортировался — и тут же пропал.
Python тут ни при чём, он сделал ровно то, что ему сказали. Просто sort и sorted выглядят как близнецы, а ведут себя по-разному. Разберёмся один раз и навсегда — а заодно пройдём всё, что встречается на практике: ключи, словари, объекты, несколько полей, кириллицу и скорость.
Чем sorted отличается от sort
Если совсем коротко: sorted() — это функция, которая возвращает новый отсортированный список и не трогает исходные данные. list.sort() — это метод списка, который сортирует его на месте и возвращает None.
scores = [42, 7, 19]fresh = sorted(scores)print(scores) # [42, 7, 19] — исходный список не тронутprint(fresh) # [7, 19, 42] — это новый списокfresh = scores.sort()print(scores) # [7, 19, 42] — теперь изменился сам списокprint(fresh) # NoneВот и разгадка бага из начала: .sort() отработал, отсортировал список, вернул None — и этот None записался поверх списка. Возврат None — сознательное решение авторов Python: метод, который меняет объект, ничего не возвращает, чтобы никто не перепутал его с функцией, создающей копию.
sorted() | list.sort() | |
| Что это | встроенная функция | метод списка |
| Что возвращает | новый список | None |
| Исходные данные | не меняются | меняются |
| С чем работает | любой итерируемый объект: список, кортеж, строка, словарь, множество | только список |
| Когда брать | нужен оригинал или у тебя не список | список большой, оригинал не нужен |
Важная деталь: sorted() всегда возвращает список, что бы ты ему ни дал.
sorted((3, 1, 2)) # [1, 2, 3] — из кортежа получился списокsorted("python") # ['h', 'n', 'o', 'p', 't', 'y']"".join(sorted("python")) # 'hnopty'Сортировка по убыванию: reverse
У обоих инструментов одинаковые параметры: key и reverse. Передавать их можно только по имени — numbers.sort(True) выдаст TypeError.
cities = ["Одесса", "Варшава", "Мадрид"]sorted(cities) # ['Варшава', 'Мадрид', 'Одесса']sorted(cities, reverse=True) # ['Одесса', 'Мадрид', 'Варшава']Сортировка по ключу: параметр key
key — главный параметр сортировки. Ты передаёшь функцию, Python вызывает её для каждого элемента и сортирует по тому, что она вернула. Сами элементы при этом не меняются.
Возьмём список сотрудников — дальше будем работать с ним:
employees = [ {"name": "Марта", "dept": "Dev", "salary": 4200}, {"name": "Денис", "dept": "QA", "salary": 2600}, {"name": "Лиза", "dept": "Dev", "salary": 3100}, {"name": "Карина", "dept": "Design", "salary": 2900},]lambda — для быстрых ключей
by_salary = sorted(employees, key=lambda e: e["salary"])itemgetter и attrgetter — то же самое, но быстрее
В модуле operator есть готовые функции для доступа к полю словаря и атрибуту объекта. Они написаны на C, поэтому работают быстрее lambda.
from operator import itemgetterby_salary = sorted(employees, key=itemgetter("salary"))Насколько быстрее? Мы замерили на свежем Python 3.15 и ста тысячах словарей: itemgetter обогнал lambda примерно на 10%. Приятно, но не революция — на коротких списках разницы не заметишь. Бери itemgetter, когда сортируешь много и часто, и lambda — когда так понятнее.
Готовые функции и методы
В key можно передать любую функцию с одним аргументом, включая встроенные. Главное — передавать саму функцию, без скобок.
words = ["banana", "kiwi", "Cherry"]sorted(words) # ['Cherry', 'banana', 'kiwi'] — заглавные идут раньшеsorted(words, key=str.lower) # ['banana', 'Cherry', 'kiwi'] — без учёта регистраsorted(words, key=len) # ['kiwi', 'banana', 'Cherry'] — по длинеСортировка по нескольким полям
Ключ-кортеж
Если key возвращает кортеж, Python сравнивает его поэлементно: сначала первое значение, при равенстве — второе и так далее. Чтобы одно из числовых полей шло по убыванию, ставим перед ним минус.
# по отделу, внутри отдела — по зарплате от большей к меньшейresult = sorted(employees, key=lambda e: (e["dept"], -e["salary"]))Два прохода — когда минус не поставить
Минус работает только с числами. Если по убыванию нужно отсортировать строку, выручает стабильность: сортировка в Python никогда не меняет порядок элементов с одинаковым ключом. Поэтому можно отсортировать в два прохода — сначала по второстепенному полю, потом по главному.
from operator import itemgetter# 1. второстепенное поле: зарплата по убываниюstep1 = sorted(employees, key=itemgetter("salary"), reverse=True)# 2. главное поле: отдел по алфавиту — порядок зарплат внутри отдела сохранитсяresult = sorted(step1, key=itemgetter("dept"))Принято считать, что кортеж быстрее, ведь проход один. Мы проверили на тех же ста тысячах словарей — и два прохода с itemgetter оказались быстрее кортежа с lambda примерно в полтора раза: 16 миллисекунд против 27. Создать сто тысяч кортежей и сравнивать их поэлементно тоже не бесплатно. Мораль простая: выбирай то, что читается понятнее, а про скорость думай, когда замер показал проблему.
Как отсортировать словарь
Сам словарь отсортировать нельзя, зато можно получить отсортированные ключи или пары и собрать из них новый словарь — с Python 3.7 словари сохраняют порядок вставки.
grades = {"Тимур": 72, "Вера": 95, "Олег": 88}sorted(grades) # ['Вера', 'Олег', 'Тимур'] — только ключи# по значению, от большего к меньшемуtop = dict(sorted(grades.items(), key=lambda kv: kv[1], reverse=True))print(top) # {'Вера': 95, 'Олег': 88, 'Тимур': 72}Нужны только лучшие N? Не сортируй всё
Частая задача — найти топ-10 самых больших значений. Первое, что приходит в голову: отсортировать и взять срез. Но сортировать миллион элементов ради десяти — расточительно. Для этого есть модуль heapq:
import heapqtop10 = heapq.nlargest(10, numbers)cheapest = heapq.nsmallest(3, products, key=lambda p: p["price"])В нашем замере на списке из миллиона чисел heapq.nlargest(10, ...) оказался почти в 18 раз быстрее, чем sorted(...)[:10]: 6 миллисекунд против 103.
Сортировка объектов
Если данные лежат в объектах, ключ строится так же, только через attrgetter:
from dataclasses import dataclassfrom operator import attrgetter@dataclassclass Employee: name: str salary: intstaff = [Employee("Марта", 4200), Employee("Денис", 2600)]sorted(staff, key=attrgetter("salary"))А если объекты нужно сортировать всегда одинаково, научи их сравниваться: для dataclass достаточно @dataclass(order=True) — сравнение пойдёт по полям в порядке объявления.
Пять ловушек, на которых ломается сортировка
1. None в списке
sorted([3, None, 1])# TypeError: '<' not supported between instances of 'NoneType' and 'int'Python 3 не умеет сравнивать None с числами. Решение — ключ, который отправляет None в конец:
sorted([3, None, 1], key=lambda x: (x is None, x)) # [1, 3, None]2. Разные типы
sorted([3, "2", 1]) упадёт с той же ошибкой: строку с числом сравнить нельзя. Обычно это значит, что данные пришли «грязными», например из CSV. Приводи к одному типу до сортировки, а не внутри неё.
3. Числа внутри строк
files = ["file10.txt", "file2.txt", "file1.txt"]sorted(files) # ['file1.txt', 'file10.txt', 'file2.txt']Строки сравниваются посимвольно, и «1» в «10» меньше, чем «2». Чтобы получить привычный человеку порядок — так называемую естественную сортировку, — разбей строку на текст и числа:
import redef natural_key(s): return [int(part) if part.isdigit() else part.lower() for part in re.split(r"(\d+)", s)]sorted(files, key=natural_key) # ['file1.txt', 'file2.txt', 'file10.txt']4. Буква «ё»
А вот ловушка, о которой в англоязычных туториалах не пишут:
sorted(["ёж", "яблоко", "ель"]) # ['ель', 'яблоко', 'ёж']Ёж оказался после яблока. Python сравнивает строки по кодам символов Unicode, а «ё» в таблице стоит не между «е» и «ж», а после «я». С заглавными то же самое: «Я» идёт раньше строчной «а».
Для сортировки по правилам языка есть модуль locale:
import localelocale.setlocale(locale.LC_COLLATE, "ru_RU.UTF-8")sorted(["ёж", "яблоко", "ель"], key=locale.strxfrm) # ['ёж', 'ель', 'яблоко']Нюанс: результат зависит от того, какие локали установлены в системе, а на Windows они называются иначе. Если сортировать по-человечески нужно в продакшене, надёжнее библиотека PyICU — она одинаково работает везде.
5. Присваивание результата sort
Тот самый баг из начала. x = x.sort() — это всегда None. Либо x.sort() отдельной строкой, либо x = sorted(x).
Что под капотом
Сортировка в Python — не учебный «пузырёк», а гибридный алгоритм, заточенный под реальные данные. С версии 2.3 это был Timsort, а с Python 3.11 его схему слияний заменили на Powersort — более надёжную, с доказанными гарантиями.
Что важно знать на практике:
- Сложность — O(n log n) в худшем случае.
- Сортировка стабильная, и это гарантировано документацией. На этом держится трюк с двумя проходами.
- Почти отсортированные данные сортируются очень быстро. Алгоритм находит уже упорядоченные куски и просто склеивает их. В нашем замере уже отсортированный список из миллиона чисел сортировался примерно в 9 раз быстрее, чем перемешанный.
Все замеры мы делали на Python 3.15.1, на ноутбуке с Apple Silicon. На другом железе абсолютные цифры будут другими, но порядок соотношений обычно сохраняется. Захочешь проверить у себя — хватит модуля timeit и пяти строк кода.
Зачем это знать, если код пишет AI
Справедливый вопрос. Попроси любую нейросеть отсортировать список словарей по зарплате — получишь рабочий код за секунду. Но AI с той же уверенностью напишет data = data.sort(), не подумает про None в данных из базы и отсортирует фамилии с «Ё» не туда. Код будет выглядеть правильно и пройдёт беглый взгляд.
Заметить такую ошибку может только тот, кто понимает, как работает сортировка. В этом и разница между тем, кто пользуется AI, и тем, кого AI делает в разы сильнее: инструмент ускоряет того, у кого есть фундамент.
Фундамент Python строится на практике, а не на чтении. На курсе Python в JavaRush это 52+ уровня задач с мгновенной проверкой и ещё 10 уровней по алгоритмам и структурам данных — где сортировки разбираются не на уровне «вызови sorted», а изнутри. Если не знаешь, с чего начать путь в Python целиком, загляни в наш роадмап Python-разработчика 2026.
Частые вопросы
В чём разница между sort и sorted в Python? sorted() — встроенная функция: принимает любой итерируемый объект и возвращает новый отсортированный список, не меняя исходные данные. list.sort() — метод списка: сортирует список на месте и возвращает None.
Почему list.sort() возвращает None? Это сознательное решение авторов Python: методы, которые меняют объект на месте, возвращают None, чтобы их не путали с функциями, создающими новый объект. Поэтому x = x.sort() записывает в переменную None.
Как отсортировать список по убыванию в Python? Передать параметр reverse=True: sorted(data, reverse=True) или data.sort(reverse=True).
Как отсортировать словарь по значению в Python? Отсортировать пары по второму элементу и собрать новый словарь: dict(sorted(d.items(), key=lambda kv: kv[1])). Для убывания добавить reverse=True.
Как отсортировать список по нескольким полям? Передать в key функцию, которая возвращает кортеж: key=lambda e: (e["dept"], -e["salary"]). Если поле нельзя развернуть минусом, например строку, отсортировать в два прохода: сначала по второстепенному полю, потом по главному — стабильность сортировки сохранит порядок.
Какой алгоритм сортировки использует Python? До версии 3.10 включительно — Timsort, с Python 3.11 — его улучшенный вариант Powersort. Оба гибридные, стабильные, работают за O(n log n) и особенно быстры на частично упорядоченных данных.